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 202a1161144..b40ac5bce90 100644 --- a/aws/internal/service/ec2/finder/finder.go +++ b/aws/internal/service/ec2/finder/finder.go @@ -54,3 +54,34 @@ func ClientVpnRouteByID(conn *ec2.EC2, routeID string) (*ec2.DescribeClientVpnRo return ClientVpnRoute(conn, endpointID, targetSubnetID, destinationCidr) } + +// 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 +} diff --git a/aws/internal/service/ec2/waiter/waiter.go b/aws/internal/service/ec2/waiter/waiter.go index 99a508abf83..3da8c1f12ae 100644 --- a/aws/internal/service/ec2/waiter/waiter.go +++ b/aws/internal/service/ec2/waiter/waiter.go @@ -16,6 +16,8 @@ const ( // General timeout for EC2 resource creations to propagate PropagationTimeout = 2 * time.Minute + + VpcPropagationTimeout = 2 * time.Minute ) // LocalGatewayRouteTableVpcAssociationAssociated waits for a LocalGatewayRouteTableVpcAssociation to return Associated diff --git a/aws/resource_aws_vpc.go b/aws/resource_aws_vpc.go index 330ac6d2086..5495cc04af2 100644 --- a/aws/resource_aws_vpc.go +++ b/aws/resource_aws_vpc.go @@ -9,14 +9,19 @@ 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/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "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 { - //lintignore:R011 + // lintignore:R011 return &schema.Resource{ Create: resourceAwsVpcCreate, Read: resourceAwsVpcRead, @@ -234,18 +239,54 @@ func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn 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) @@ -273,7 +314,7 @@ func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error { d.Set("ipv6_cidr_block", "") for _, a := range vpc.Ipv6CidrBlockAssociationSet { - if aws.StringValue(a.Ipv6CidrBlockState.State) == ec2.VpcCidrBlockStateCodeAssociated { //we can only ever have 1 IPv6 block associated at once + if aws.StringValue(a.Ipv6CidrBlockState.State) == ec2.VpcCidrBlockStateCodeAssociated { // we can only ever have 1 IPv6 block associated at once d.Set("assign_generated_ipv6_cidr_block", true) d.Set("ipv6_association_id", a.AssociationId) d.Set("ipv6_cidr_block", a.Ipv6CidrBlock)