Skip to content

Commit

Permalink
resource/aws_vpc_dhcp_options_association: Handle read-after-create e…
Browse files Browse the repository at this point in the history
…ventual consistency (hashicorp#18472)

* resource/aws_vpc_dhcp_options_association: Handle read-after-create eventual consistency

Reference: hashicorp#16142
Reference: hashicorp#16796

Output from acceptance testing in AWS Commercial:

```
--- PASS: TestAccAWSDHCPOptionsAssociation_disappears_vpc (19.27s)
--- PASS: TestAccAWSDHCPOptionsAssociation_disappears_dhcp (25.61s)
--- PASS: TestAccAWSDHCPOptionsAssociation_basic (25.84s)
```

Output from acceptance testing in AWS GovCloud (US):

```
--- PASS: TestAccAWSDHCPOptionsAssociation_disappears_vpc (21.51s)
--- PASS: TestAccAWSDHCPOptionsAssociation_disappears_dhcp (27.55s)
--- PASS: TestAccAWSDHCPOptionsAssociation_basic (28.10s)
```

* Update CHANGELOG for hashicorp#18472

* r/vpc_dhcp_options_association: Tweak delete

Co-authored-by: Dirk Avery <dirk.avery@gmail.com>
  • Loading branch information
2 people authored and staebler committed Dec 15, 2021
1 parent 9db6895 commit 12a0073
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 14 deletions.
3 changes: 3 additions & 0 deletions .changelog/18472.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
resource/aws_vpc_dhcp_options_association: Handle read-after-create eventual consistency
```
64 changes: 52 additions & 12 deletions aws/resource_aws_vpc_dhcp_options_association.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
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 resourceAwsVpcDhcpOptionsAssociation() *schema.Resource {
Expand All @@ -23,6 +29,7 @@ func resourceAwsVpcDhcpOptionsAssociation() *schema.Resource {
"vpc_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"dhcp_options_id": {
Expand Down Expand Up @@ -79,22 +86,47 @@ func resourceAwsVpcDhcpOptionsAssociationCreate(d *schema.ResourceData, meta int

func resourceAwsVpcDhcpOptionsAssociationRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn
// Get the VPC that this association belongs to
vpcRaw, _, err := VPCStateRefreshFunc(conn, d.Get("vpc_id").(string))()

if err != nil {
return err
}
var vpc *ec2.Vpc

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

vpc, err = finder.VpcByID(conn, d.Get("vpc_id").(string))

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

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

if d.IsNewResource() && aws.StringValue(vpc.DhcpOptionsId) != d.Get("dhcp_options_id").(string) {
return resource.RetryableError(&resource.NotFoundError{
LastError: fmt.Errorf("EC2 VPC DHCP Options Association (%s) not found", d.Id()),
})
}

if vpcRaw == nil {
return nil
})

if tfresource.TimedOut(err) {
vpc, err = finder.VpcByID(conn, d.Get("vpc_id").(string))
}

vpc := vpcRaw.(*ec2.Vpc)
if aws.StringValue(vpc.VpcId) != d.Get("vpc_id") ||
aws.StringValue(vpc.DhcpOptionsId) != d.Get("dhcp_options_id") {
log.Printf("[INFO] It seems the DHCP Options association is gone. Deleting reference from Graph...")
if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidVpcIDNotFound) {
log.Printf("[WARN] EC2 VPC DHCP Options Association (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err != nil {
return fmt.Errorf("error reading EC2 VPC DHCP Options Association (%s): %w", d.Id(), err)
}

if vpc == nil {
return fmt.Errorf("error reading EC2 VPC DHCP Options Association (%s): empty response", d.Id())
}

d.Set("vpc_id", vpc.VpcId)
Expand All @@ -108,18 +140,26 @@ func resourceAwsVpcDhcpOptionsAssociationUpdate(d *schema.ResourceData, meta int
return resourceAwsVpcDhcpOptionsAssociationCreate(d, meta)
}

const VPCDefaultOptionsID = "default"

// AWS does not provide an API to disassociate a DHCP Options set from a VPC.
// So, we do this by setting the VPC to the default DHCP Options Set.
func resourceAwsVpcDhcpOptionsAssociationDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn

log.Printf("[INFO] Disassociating DHCP Options Set %s from VPC %s...", d.Get("dhcp_options_id"), d.Get("vpc_id"))

if d.Get("dhcp_options_id").(string) == VPCDefaultOptionsID {
// definition of deleted is DhcpOptionsId being equal to "default", nothing to do
return nil
}

_, err := conn.AssociateDhcpOptions(&ec2.AssociateDhcpOptionsInput{
DhcpOptionsId: aws.String("default"),
DhcpOptionsId: aws.String(VPCDefaultOptionsID),
VpcId: aws.String(d.Get("vpc_id").(string)),
})

if isAWSErr(err, "InvalidVpcID.NotFound", "") {
if tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidVpcIDNotFound) {
return nil
}

Expand Down
29 changes: 27 additions & 2 deletions aws/resource_aws_vpc_dhcp_options_association_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,31 @@ func TestAccAWSDHCPOptionsAssociation_disappears_dhcp(t *testing.T) {
})
}

func TestAccAWSDHCPOptionsAssociation_disappears(t *testing.T) {
var v ec2.Vpc
var d ec2.DhcpOptions
resourceName := "aws_vpc_dhcp_options_association.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ErrorCheck: testAccErrorCheck(t, ec2.EndpointsID),
Providers: testAccProviders,
CheckDestroy: testAccCheckDHCPOptionsAssociationDestroy,
Steps: []resource.TestStep{
{
Config: testAccDHCPOptionsAssociationConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckDHCPOptionsExists("aws_vpc_dhcp_options.test", &d),
testAccCheckVpcExists("aws_vpc.test", &v),
testAccCheckDHCPOptionsAssociationExist(resourceName, &v),
testAccCheckResourceDisappears(testAccProvider, resourceAwsVpcDhcpOptionsAssociation(), resourceName),
),
ExpectNonEmptyPlan: true,
},
},
})
}

func testAccDHCPOptionsAssociationVPCImportIdFunc(resourceName string) resource.ImportStateIdFunc {
return func(s *terraform.State) (string, error) {
rs, ok := s.RootModule().Resources[resourceName]
Expand All @@ -111,8 +136,8 @@ func testAccCheckDHCPOptionsAssociationDestroy(s *terraform.State) error {
return err
}

if len(vpcs) > 0 {
return fmt.Errorf("DHCP Options association is still associated to %d VPCs.", len(vpcs))
if rs.Primary.Attributes["dhcp_options_id"] != VPCDefaultOptionsID && len(vpcs) > 0 {
return fmt.Errorf("vpc_dhcp_options_association (%s) is still associated to %d VPCs.", rs.Primary.Attributes["dhcp_options_id"], len(vpcs))
}
}

Expand Down

0 comments on commit 12a0073

Please sign in to comment.