Skip to content

Commit e257ac0

Browse files
authored
resource/aws_vpc: Apply attribute waiter logic to enable_dns_hostnames and enable_dns_support attributes (#17461)
Reference: #16697 This resource attribute has long been the cause of flakey acceptance testing across the codebase, such as: ``` === CONT TestAccAWSEMRInstanceGroup_InstanceCount TestAccAWSEMRInstanceGroup_InstanceCount: resource_aws_emr_instance_group_test.go:181: Step 1/3 error: After applying this test step, the plan was not empty. stdout: An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: ~ resource "aws_vpc" "main" { ~ enable_dns_hostnames = false -> true id = "vpc-0b7f7f7c1601ee31f" } Plan: 0 to add, 1 to change, 0 to destroy. --- FAIL: TestAccAWSEMRInstanceGroup_InstanceCount (809.62s) ``` Adding logic, similar to `SubnetMapCustomerOwnedIpOnLaunchUpdated` in #16676 can be used to ensure the attribute value has flipped correctly after calling the `ModifyVpcAttribute` API. This type of waiter logic will be documented in an upcoming Retries and Waiters section of the Contribution Guide. Output from acceptance testing in AWS Commercial: ``` --- PASS: TestAccAWSVpc_AssignGeneratedIpv6CidrBlock (79.17s) --- PASS: TestAccAWSVpc_basic (33.46s) --- PASS: TestAccAWSVpc_bothDnsOptionsSet (42.70s) --- PASS: TestAccAWSVpc_classiclinkDnsSupportOptionSet (36.56s) --- PASS: TestAccAWSVpc_classiclinkOptionSet (34.72s) --- PASS: TestAccAWSVpc_coreMismatchedDiffs (27.89s) --- PASS: TestAccAWSVpc_DisabledDnsSupport (42.71s) --- PASS: TestAccAWSVpc_disappears (20.00s) --- PASS: TestAccAWSVpc_ignoreTags (58.33s) --- PASS: TestAccAWSVpc_tags (75.02s) --- PASS: TestAccAWSVpc_Tenancy (77.02s) --- PASS: TestAccAWSVpc_update (62.26s) --- PASS: TestAccDataSourceAwsVpc_basic (32.46s) --- PASS: TestAccDataSourceAwsVpc_ipv6Associated (33.00s) --- PASS: TestAccDataSourceAwsVpc_multipleCidr (56.10s) --- PASS: TestAccAWSEMRCluster_additionalInfo (407.52s) --- PASS: TestAccAWSEMRInstanceGroup_InstanceCount (881.55s) ``` Output from acceptance testing in AWS GovCloud (US): ``` --- FAIL: TestAccAWSVpc_classiclinkDnsSupportOptionSet (17.50s) # #17460 --- FAIL: TestAccAWSVpc_classiclinkOptionSet (18.04s) # #17460 --- PASS: TestAccAWSVpc_AssignGeneratedIpv6CidrBlock (95.03s) --- PASS: TestAccAWSVpc_basic (42.63s) --- PASS: TestAccAWSVpc_bothDnsOptionsSet (52.27s) --- PASS: TestAccAWSVpc_coreMismatchedDiffs (38.50s) --- PASS: TestAccAWSVpc_DisabledDnsSupport (53.42s) --- PASS: TestAccAWSVpc_disappears (28.33s) --- PASS: TestAccAWSVpc_ignoreTags (69.32s) --- PASS: TestAccAWSVpc_tags (90.39s) --- PASS: TestAccAWSVpc_Tenancy (94.96s) --- PASS: TestAccAWSVpc_update (72.18s) --- PASS: TestAccDataSourceAwsVpc_basic (32.39s) --- PASS: TestAccDataSourceAwsVpc_ipv6Associated (51.24s) --- PASS: TestAccDataSourceAwsVpc_multipleCidr (67.27s) --- PASS: TestAccAWSEMRCluster_additionalInfo (355.27s) --- PASS: TestAccAWSEMRInstanceGroup_InstanceCount (856.03s) ```
1 parent 33309dd commit e257ac0

File tree

6 files changed

+133
-45
lines changed

6 files changed

+133
-45
lines changed

aws/data_source_aws_vpc.go

+11-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/aws/aws-sdk-go/service/ec2"
1010
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1111
"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
12+
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder"
1213
)
1314

1415
func dataSourceAwsVpc() *schema.Resource {
@@ -216,17 +217,21 @@ func dataSourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error {
216217
d.Set("ipv6_cidr_block", vpc.Ipv6CidrBlockAssociationSet[0].Ipv6CidrBlock)
217218
}
218219

219-
attResp, err := awsVpcDescribeVpcAttribute("enableDnsSupport", aws.StringValue(vpc.VpcId), conn)
220+
enableDnsHostnames, err := finder.VpcAttribute(conn, aws.StringValue(vpc.VpcId), ec2.VpcAttributeNameEnableDnsHostnames)
221+
220222
if err != nil {
221-
return err
223+
return fmt.Errorf("error reading EC2 VPC (%s) Attribute (%s): %w", aws.StringValue(vpc.VpcId), ec2.VpcAttributeNameEnableDnsHostnames, err)
222224
}
223-
d.Set("enable_dns_support", attResp.EnableDnsSupport.Value)
224225

225-
attResp, err = awsVpcDescribeVpcAttribute("enableDnsHostnames", aws.StringValue(vpc.VpcId), conn)
226+
d.Set("enable_dns_hostnames", enableDnsHostnames)
227+
228+
enableDnsSupport, err := finder.VpcAttribute(conn, aws.StringValue(vpc.VpcId), ec2.VpcAttributeNameEnableDnsSupport)
229+
226230
if err != nil {
227-
return err
231+
return fmt.Errorf("error reading EC2 VPC (%s) Attribute (%s): %w", aws.StringValue(vpc.VpcId), ec2.VpcAttributeNameEnableDnsSupport, err)
228232
}
229-
d.Set("enable_dns_hostnames", attResp.EnableDnsHostnames.Value)
233+
234+
d.Set("enable_dns_support", enableDnsSupport)
230235

231236
routeTableId, err := resourceAwsVpcSetMainRouteTable(conn, aws.StringValue(vpc.VpcId))
232237
if err != nil {

aws/internal/service/ec2/errors.go

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ const (
4444
ErrCodeInvalidSubnetIDNotFound = "InvalidSubnetID.NotFound"
4545
)
4646

47+
const (
48+
ErrCodeInvalidVpcIDNotFound = "InvalidVpcID.NotFound"
49+
)
50+
4751
const (
4852
ErrCodeInvalidVpcEndpointIdNotFound = "InvalidVpcEndpointId.NotFound"
4953
ErrCodeInvalidVpcEndpointServiceIdNotFound = "InvalidVpcEndpointServiceId.NotFound"

aws/internal/service/ec2/finder/finder.go

+35
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,41 @@ func TransitGatewayPrefixListReferenceByID(conn *ec2.EC2, resourceID string) (*e
175175
return TransitGatewayPrefixListReference(conn, transitGatewayRouteTableID, prefixListID)
176176
}
177177

178+
// VpcAttribute looks up a VPC attribute.
179+
func VpcAttribute(conn *ec2.EC2, vpcID string, attribute string) (*bool, error) {
180+
input := &ec2.DescribeVpcAttributeInput{
181+
Attribute: aws.String(attribute),
182+
VpcId: aws.String(vpcID),
183+
}
184+
185+
output, err := conn.DescribeVpcAttribute(input)
186+
187+
if err != nil {
188+
return nil, err
189+
}
190+
191+
if output == nil {
192+
return nil, nil
193+
}
194+
195+
switch attribute {
196+
case ec2.VpcAttributeNameEnableDnsHostnames:
197+
if output.EnableDnsHostnames == nil {
198+
return nil, nil
199+
}
200+
201+
return output.EnableDnsHostnames.Value, nil
202+
case ec2.VpcAttributeNameEnableDnsSupport:
203+
if output.EnableDnsSupport == nil {
204+
return nil, nil
205+
}
206+
207+
return output.EnableDnsSupport.Value, nil
208+
}
209+
210+
return nil, fmt.Errorf("unimplemented VPC attribute: %s", attribute)
211+
}
212+
178213
// VpcPeeringConnectionByID returns the VPC peering connection corresponding to the specified identifier.
179214
// Returns nil and potentially an error if no VPC peering connection is found.
180215
func VpcPeeringConnectionByID(conn *ec2.EC2, id string) (*ec2.VpcPeeringConnection, error) {

aws/internal/service/ec2/waiter/status.go

+21
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,27 @@ func TransitGatewayPrefixListReferenceState(conn *ec2.EC2, transitGatewayRouteTa
331331
}
332332
}
333333

334+
// VpcAttribute fetches the Vpc and its attribute value
335+
func VpcAttribute(conn *ec2.EC2, id string, attribute string) resource.StateRefreshFunc {
336+
return func() (interface{}, string, error) {
337+
attributeValue, err := finder.VpcAttribute(conn, id, attribute)
338+
339+
if tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidVpcIDNotFound) {
340+
return nil, "", nil
341+
}
342+
343+
if err != nil {
344+
return nil, "", err
345+
}
346+
347+
if attributeValue == nil {
348+
return nil, "", nil
349+
}
350+
351+
return attributeValue, strconv.FormatBool(aws.BoolValue(attributeValue)), nil
352+
}
353+
}
354+
334355
const (
335356
vpcPeeringConnectionStatusNotFound = "NotFound"
336357
vpcPeeringConnectionStatusUnknown = "Unknown"

aws/internal/service/ec2/waiter/waiter.go

+22
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,28 @@ func TransitGatewayPrefixListReferenceStateUpdated(conn *ec2.EC2, transitGateway
368368
return nil, err
369369
}
370370

371+
const (
372+
VpcAttributePropagationTimeout = 5 * time.Minute
373+
)
374+
375+
func VpcAttributeUpdated(conn *ec2.EC2, vpcID string, attribute string, expectedValue bool) (*ec2.Vpc, error) {
376+
stateConf := &resource.StateChangeConf{
377+
Target: []string{strconv.FormatBool(expectedValue)},
378+
Refresh: VpcAttribute(conn, vpcID, attribute),
379+
Timeout: VpcAttributePropagationTimeout,
380+
Delay: 10 * time.Second,
381+
MinTimeout: 3 * time.Second,
382+
}
383+
384+
outputRaw, err := stateConf.WaitForState()
385+
386+
if output, ok := outputRaw.(*ec2.Vpc); ok {
387+
return output, err
388+
}
389+
390+
return nil, err
391+
}
392+
371393
const (
372394
VpnGatewayVpcAttachmentAttachedTimeout = 15 * time.Minute
373395

aws/resource_aws_vpc.go

+40-39
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1515
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1616
"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
17+
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/finder"
18+
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/waiter"
1719
)
1820

1921
func resourceAwsVpc() *schema.Resource {
@@ -183,7 +185,11 @@ func resourceAwsVpcCreate(d *schema.ResourceData, meta interface{}) error {
183185
}
184186

185187
if _, err := conn.ModifyVpcAttribute(input); err != nil {
186-
return fmt.Errorf("error enabling VPC (%s) DNS hostnames: %s", d.Id(), err)
188+
return fmt.Errorf("error enabling EC2 VPC (%s) DNS Hostnames: %w", d.Id(), err)
189+
}
190+
191+
if _, err := waiter.VpcAttributeUpdated(conn, d.Id(), ec2.VpcAttributeNameEnableDnsHostnames, d.Get("enable_dns_hostnames").(bool)); err != nil {
192+
return fmt.Errorf("error waiting for EC2 VPC (%s) DNS Hostnames to enable: %w", d.Id(), err)
187193
}
188194
}
189195

@@ -199,7 +205,11 @@ func resourceAwsVpcCreate(d *schema.ResourceData, meta interface{}) error {
199205
}
200206

201207
if _, err := conn.ModifyVpcAttribute(input); err != nil {
202-
return fmt.Errorf("error disabling VPC (%s) DNS support: %s", d.Id(), err)
208+
return fmt.Errorf("error disabling EC2 VPC (%s) DNS Support: %w", d.Id(), err)
209+
}
210+
211+
if _, err := waiter.VpcAttributeUpdated(conn, d.Id(), ec2.VpcAttributeNameEnableDnsSupport, d.Get("enable_dns_support").(bool)); err != nil {
212+
return fmt.Errorf("error waiting for EC2 VPC (%s) DNS Support to disable: %w", d.Id(), err)
203213
}
204214
}
205215

@@ -276,17 +286,21 @@ func resourceAwsVpcRead(d *schema.ResourceData, meta interface{}) error {
276286
}
277287
}
278288

279-
resp, err := awsVpcDescribeVpcAttribute("enableDnsSupport", vpcid, conn)
289+
enableDnsHostnames, err := finder.VpcAttribute(conn, aws.StringValue(vpc.VpcId), ec2.VpcAttributeNameEnableDnsHostnames)
290+
280291
if err != nil {
281-
return err
292+
return fmt.Errorf("error reading EC2 VPC (%s) Attribute (%s): %w", aws.StringValue(vpc.VpcId), ec2.VpcAttributeNameEnableDnsHostnames, err)
282293
}
283-
d.Set("enable_dns_support", resp.EnableDnsSupport.Value)
284294

285-
resp, err = awsVpcDescribeVpcAttribute("enableDnsHostnames", vpcid, conn)
295+
d.Set("enable_dns_hostnames", enableDnsHostnames)
296+
297+
enableDnsSupport, err := finder.VpcAttribute(conn, aws.StringValue(vpc.VpcId), ec2.VpcAttributeNameEnableDnsSupport)
298+
286299
if err != nil {
287-
return err
300+
return fmt.Errorf("error reading EC2 VPC (%s) Attribute (%s): %w", aws.StringValue(vpc.VpcId), ec2.VpcAttributeNameEnableDnsSupport, err)
288301
}
289-
d.Set("enable_dns_hostnames", resp.EnableDnsHostnames.Value)
302+
303+
d.Set("enable_dns_support", enableDnsSupport)
290304

291305
describeClassiclinkOpts := &ec2.DescribeVpcClassicLinkInput{
292306
VpcIds: []*string{&vpcid},
@@ -362,38 +376,38 @@ func resourceAwsVpcUpdate(d *schema.ResourceData, meta interface{}) error {
362376

363377
vpcid := d.Id()
364378
if d.HasChange("enable_dns_hostnames") {
365-
val := d.Get("enable_dns_hostnames").(bool)
366-
modifyOpts := &ec2.ModifyVpcAttributeInput{
367-
VpcId: &vpcid,
379+
input := &ec2.ModifyVpcAttributeInput{
380+
VpcId: aws.String(d.Id()),
368381
EnableDnsHostnames: &ec2.AttributeBooleanValue{
369-
Value: &val,
382+
Value: aws.Bool(d.Get("enable_dns_hostnames").(bool)),
370383
},
371384
}
372385

373-
log.Printf(
374-
"[INFO] Modifying enable_dns_hostnames vpc attribute for %s: %s",
375-
d.Id(), modifyOpts)
376-
if _, err := conn.ModifyVpcAttribute(modifyOpts); err != nil {
377-
return err
386+
if _, err := conn.ModifyVpcAttribute(input); err != nil {
387+
return fmt.Errorf("error updating EC2 VPC (%s) DNS Hostnames: %w", d.Id(), err)
388+
}
389+
390+
if _, err := waiter.VpcAttributeUpdated(conn, d.Id(), ec2.VpcAttributeNameEnableDnsHostnames, d.Get("enable_dns_hostnames").(bool)); err != nil {
391+
return fmt.Errorf("error waiting for EC2 VPC (%s) DNS Hostnames update: %w", d.Id(), err)
378392
}
379393
}
380394

381395
_, hasEnableDnsSupportOption := d.GetOk("enable_dns_support")
382396

383397
if !hasEnableDnsSupportOption || d.HasChange("enable_dns_support") {
384-
val := d.Get("enable_dns_support").(bool)
385-
modifyOpts := &ec2.ModifyVpcAttributeInput{
386-
VpcId: &vpcid,
398+
input := &ec2.ModifyVpcAttributeInput{
399+
VpcId: aws.String(d.Id()),
387400
EnableDnsSupport: &ec2.AttributeBooleanValue{
388-
Value: &val,
401+
Value: aws.Bool(d.Get("enable_dns_support").(bool)),
389402
},
390403
}
391404

392-
log.Printf(
393-
"[INFO] Modifying enable_dns_support vpc attribute for %s: %s",
394-
d.Id(), modifyOpts)
395-
if _, err := conn.ModifyVpcAttribute(modifyOpts); err != nil {
396-
return err
405+
if _, err := conn.ModifyVpcAttribute(input); err != nil {
406+
return fmt.Errorf("error updating EC2 VPC (%s) DNS Support: %w", d.Id(), err)
407+
}
408+
409+
if _, err := waiter.VpcAttributeUpdated(conn, d.Id(), ec2.VpcAttributeNameEnableDnsSupport, d.Get("enable_dns_support").(bool)); err != nil {
410+
return fmt.Errorf("error waiting for EC2 VPC (%s) DNS Support update: %w", d.Id(), err)
397411
}
398412
}
399413

@@ -733,19 +747,6 @@ func resourceAwsVpcInstanceImport(
733747
return []*schema.ResourceData{d}, nil
734748
}
735749

736-
func awsVpcDescribeVpcAttribute(attribute string, vpcId string, conn *ec2.EC2) (*ec2.DescribeVpcAttributeOutput, error) {
737-
describeAttrOpts := &ec2.DescribeVpcAttributeInput{
738-
Attribute: aws.String(attribute),
739-
VpcId: aws.String(vpcId),
740-
}
741-
resp, err := conn.DescribeVpcAttribute(describeAttrOpts)
742-
if err != nil {
743-
return nil, err
744-
}
745-
746-
return resp, nil
747-
}
748-
749750
// vpcDescribe returns EC2 API information about the specified VPC.
750751
// If the VPC doesn't exist, return nil.
751752
func vpcDescribe(conn *ec2.EC2, vpcId string) (*ec2.Vpc, error) {

0 commit comments

Comments
 (0)