diff --git a/.changelog/18391.txt b/.changelog/18391.txt new file mode 100644 index 00000000000..e7a0ee19eff --- /dev/null +++ b/.changelog/18391.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_vpc: Handle EC2 eventual consistency errors on creation +``` diff --git a/aws/internal/service/ec2/finder/finder.go b/aws/internal/service/ec2/finder/finder.go index c1a9fcde62a..9749e368a44 100644 --- a/aws/internal/service/ec2/finder/finder.go +++ b/aws/internal/service/ec2/finder/finder.go @@ -285,6 +285,37 @@ func VpcAttribute(conn *ec2.EC2, vpcID string, attribute string) (*bool, error) return nil, fmt.Errorf("unimplemented VPC attribute: %s", attribute) } +// VpcByID looks up a Vpc by ID. When not found, returns nil and potentially an API error. +func VpcByID(conn *ec2.EC2, id string) (*ec2.Vpc, error) { + input := &ec2.DescribeVpcsInput{ + VpcIds: aws.StringSlice([]string{id}), + } + + output, err := conn.DescribeVpcs(input) + + if err != nil { + return nil, err + } + + if output == nil { + return nil, nil + } + + for _, vpc := range output.Vpcs { + if vpc == nil { + continue + } + + if aws.StringValue(vpc.VpcId) != id { + continue + } + + return vpc, nil + } + + return nil, nil +} + // 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) { diff --git a/aws/internal/service/ec2/waiter/waiter.go b/aws/internal/service/ec2/waiter/waiter.go index 503210314bf..7c3a06ebf89 100644 --- a/aws/internal/service/ec2/waiter/waiter.go +++ b/aws/internal/service/ec2/waiter/waiter.go @@ -369,6 +369,7 @@ func TransitGatewayPrefixListReferenceStateUpdated(conn *ec2.EC2, transitGateway } const ( + VpcPropagationTimeout = 2 * time.Minute VpcAttributePropagationTimeout = 5 * time.Minute ) diff --git a/aws/resource_aws_vpc.go b/aws/resource_aws_vpc.go index b01bcbddf87..6fe280b4cf8 100644 --- a/aws/resource_aws_vpc.go +++ b/aws/resource_aws_vpc.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/aws/arn" "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/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -17,6 +18,7 @@ import ( "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" "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 resourceAwsVpc() *schema.Resource { @@ -250,18 +252,54 @@ func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error { defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig - // Refresh the VPC state - vpcRaw, _, err := VPCStateRefreshFunc(conn, d.Id())() + var vpc *ec2.Vpc + + err := resource.Retry(waiter.VpcPropagationTimeout, func() *resource.RetryError { + var err error + + vpc, err = finder.VpcByID(conn, d.Id()) + + if d.IsNewResource() && tfawserr.ErrCodeEquals(err, "InvalidVpcID.NotFound") { + return resource.RetryableError(err) + } + + if err != nil { + return resource.NonRetryableError(err) + } + + if d.IsNewResource() && vpc == nil { + return resource.RetryableError(&resource.NotFoundError{ + LastError: fmt.Errorf("EC2 VPC (%s) not found", d.Id()), + }) + } + + return nil + }) + + if tfresource.TimedOut(err) { + vpc, err = finder.VpcByID(conn, d.Id()) + } + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, "InvalidVpcID.NotFound") { + log.Printf("[WARN] EC2 VPC (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + if err != nil { - return err + return fmt.Errorf("error reading EC2 VPC (%s): %w", d.Id(), err) } - if vpcRaw == nil { + + if vpc == nil { + if d.IsNewResource() { + return fmt.Errorf("error reading EC2 VPC (%s): not found after creation", d.Id()) + } + + log.Printf("[WARN] EC2 VPC (%s) not found, removing from state", d.Id()) d.SetId("") return nil } - // VPC stuff - vpc := vpcRaw.(*ec2.Vpc) vpcid := d.Id() d.Set("cidr_block", vpc.CidrBlock) d.Set("dhcp_options_id", vpc.DhcpOptionsId)