Skip to content

Commit bee90d4

Browse files
committed
Allows secondary_private_ip_address_count to be updated in place
1 parent edf4b7f commit bee90d4

5 files changed

Lines changed: 183 additions & 7 deletions

File tree

.changelog/47477.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
resource/aws_nat_gateway: Allow updating `secondary_private_ip_address_count` in-place for private NAT gateways
3+
```

internal/service/ec2/status.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1725,6 +1725,39 @@ func statusNATGatewayAddressByNATGatewayIDAndPrivateIP(conn *ec2.Client, natGate
17251725
}
17261726
}
17271727

1728+
func statusNATGatewaySecondaryPrivateIPAddressCount(conn *ec2.Client, natGatewayID string, expectedCount int) retry.StateRefreshFunc {
1729+
return func(ctx context.Context) (any, string, error) {
1730+
output, err := findNATGatewayByID(ctx, conn, natGatewayID)
1731+
1732+
if retry.NotFound(err) {
1733+
return nil, "", nil
1734+
}
1735+
1736+
if err != nil {
1737+
return nil, "", err
1738+
}
1739+
1740+
secondaryCount := 0
1741+
for _, natGatewayAddress := range output.NatGatewayAddresses {
1742+
if !aws.ToBool(natGatewayAddress.IsPrimary) && aws.ToString(natGatewayAddress.PrivateIp) != "" {
1743+
if natGatewayAddress.Status == awstypes.NatGatewayAddressStatusFailed {
1744+
return output, string(natGatewayAddress.Status), nil
1745+
}
1746+
1747+
if natGatewayAddress.Status == awstypes.NatGatewayAddressStatusSucceeded {
1748+
secondaryCount++
1749+
}
1750+
}
1751+
}
1752+
1753+
if secondaryCount == expectedCount {
1754+
return output, "ready", nil
1755+
}
1756+
1757+
return output, "pending", nil
1758+
}
1759+
}
1760+
17281761
func statusNATGatewayAttachedAppliances(conn *ec2.Client, id string) retry.StateRefreshFunc {
17291762
return func(ctx context.Context) (any, string, error) {
17301763
output, err := findNATGatewayByID(ctx, conn, id)

internal/service/ec2/vpc_nat_gateway.go

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,12 @@ func resourceNATGatewayUpdate(ctx context.Context, d *schema.ResourceData, meta
432432
case awstypes.AvailabilityModeZonal:
433433
switch awstypes.ConnectivityType(d.Get("connectivity_type").(string)) {
434434
case awstypes.ConnectivityTypePrivate:
435-
if d.HasChanges("secondary_private_ip_addresses") {
435+
countRaw := d.GetRawConfig().GetAttr("secondary_private_ip_address_count")
436+
countConfigured := countRaw.IsKnown() && !countRaw.IsNull()
437+
addressesRaw := d.GetRawConfig().GetAttr("secondary_private_ip_addresses")
438+
addressesConfigured := addressesRaw.IsKnown() && !addressesRaw.IsNull()
439+
440+
if addressesConfigured && d.HasChange("secondary_private_ip_addresses") {
436441
o, n := d.GetChange("secondary_private_ip_addresses")
437442
os, ns := o.(*schema.Set), n.(*schema.Set)
438443

@@ -474,6 +479,57 @@ func resourceNATGatewayUpdate(ctx context.Context, d *schema.ResourceData, meta
474479
}
475480
}
476481
}
482+
483+
if countConfigured && d.HasChange("secondary_private_ip_address_count") {
484+
o, n := d.GetChange("secondary_private_ip_address_count")
485+
oldCount, newCount := o.(int), n.(int)
486+
487+
delta := newCount - oldCount
488+
489+
if delta > 0 {
490+
input := &ec2.AssignPrivateNatGatewayAddressInput{
491+
NatGatewayId: aws.String(d.Id()),
492+
PrivateIpAddressCount: aws.Int32(int32(delta)),
493+
}
494+
495+
_, err := conn.AssignPrivateNatGatewayAddress(ctx, input)
496+
497+
if err != nil {
498+
return sdkdiag.AppendErrorf(diags, "assigning EC2 NAT Gateway (%s) private IP address count: %s", d.Id(), err)
499+
}
500+
501+
if _, err := waitNATGatewaySecondaryPrivateIPAddressCount(ctx, conn, d.Id(), newCount, d.Timeout(schema.TimeoutUpdate)); err != nil {
502+
return sdkdiag.AppendErrorf(diags, "waiting for EC2 NAT Gateway (%s) secondary private IP address count (%d): %s", d.Id(), newCount, err)
503+
}
504+
}
505+
506+
if delta < 0 {
507+
removeCount := -delta
508+
509+
sIPs, _ := d.GetChange("secondary_private_ip_addresses")
510+
secondaryPrivateIPs := sIPs.(*schema.Set).List()
511+
512+
privateIPsToUnassign := secondaryPrivateIPs[:removeCount]
513+
514+
input := &ec2.UnassignPrivateNatGatewayAddressInput{
515+
NatGatewayId: aws.String(d.Id()),
516+
PrivateIpAddresses: flex.ExpandStringValueList(privateIPsToUnassign),
517+
MaxDrainDurationSeconds: aws.Int32(50),
518+
}
519+
520+
_, err := conn.UnassignPrivateNatGatewayAddress(ctx, input)
521+
522+
if err != nil {
523+
return sdkdiag.AppendErrorf(diags, "unassigning EC2 NAT Gateway (%s) private IP addresses: %s", d.Id(), err)
524+
}
525+
526+
for _, privateIP := range flex.ExpandStringValueList(privateIPsToUnassign) {
527+
if _, err := waitNATGatewayAddressUnassigned(ctx, conn, d.Id(), privateIP, d.Timeout(schema.TimeoutUpdate)); err != nil {
528+
return sdkdiag.AppendErrorf(diags, "waiting for EC2 NAT Gateway (%s) private IP address (%s) unassign: %s", d.Id(), privateIP, err)
529+
}
530+
}
531+
}
532+
}
477533
case awstypes.ConnectivityTypePublic:
478534
if !d.GetRawConfig().GetAttr("secondary_allocation_ids").IsNull() && d.HasChanges("secondary_allocation_ids") {
479535
o, n := d.GetChange("secondary_allocation_ids")
@@ -654,15 +710,18 @@ func resourceNATGatewayCustomizeDiff(ctx context.Context, diff *schema.ResourceD
654710
return fmt.Errorf(`secondary_allocation_ids is not supported with connectivity_type = "%s"`, connectivityType)
655711
}
656712

657-
if diff.Id() != "" && diff.HasChange("secondary_private_ip_address_count") {
658-
if v := diff.GetRawConfig().GetAttr("secondary_private_ip_address_count"); v.IsKnown() && !v.IsNull() {
659-
if err := diff.ForceNew("secondary_private_ip_address_count"); err != nil {
660-
return fmt.Errorf("setting secondary_private_ip_address_count to ForceNew: %w", err)
661-
}
713+
countRaw := diff.GetRawConfig().GetAttr("secondary_private_ip_address_count")
714+
countConfigured := countRaw.IsKnown() && !countRaw.IsNull()
715+
addressesRaw := diff.GetRawConfig().GetAttr("secondary_private_ip_addresses")
716+
addressesConfigured := addressesRaw.IsKnown() && !addressesRaw.IsNull()
717+
718+
if diff.Id() != "" && countConfigured && diff.HasChange("secondary_private_ip_address_count") {
719+
if err := diff.SetNewComputed("secondary_private_ip_addresses"); err != nil {
720+
return fmt.Errorf("setting secondary_private_ip_addresses to Computed: %w", err)
662721
}
663722
}
664723

665-
if diff.Id() != "" && diff.HasChange("secondary_private_ip_addresses") {
724+
if diff.Id() != "" && addressesConfigured && diff.HasChange("secondary_private_ip_addresses") {
666725
if err := diff.SetNewComputed("secondary_private_ip_address_count"); err != nil {
667726
return fmt.Errorf("setting secondary_private_ip_address_count to Computed: %w", err)
668727
}

internal/service/ec2/vpc_nat_gateway_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,62 @@ func TestAccVPCNATGateway_secondaryPrivateIPAddressCountToSpecific(t *testing.T)
430430
})
431431
}
432432

433+
func TestAccVPCNATGateway_secondaryPrivateIPAddressCount_updateInPlace(t *testing.T) {
434+
ctx := acctest.Context(t)
435+
var natGateway awstypes.NatGateway
436+
resourceName := "aws_nat_gateway.test"
437+
rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix)
438+
439+
countStart := 0
440+
countUp := 3
441+
countDown := 1
442+
443+
acctest.ParallelTest(ctx, t, resource.TestCase{
444+
PreCheck: func() { acctest.PreCheck(ctx, t) },
445+
ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID),
446+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
447+
CheckDestroy: testAccCheckNATGatewayDestroy(ctx, t),
448+
Steps: []resource.TestStep{
449+
{
450+
Config: testAccVPCNATGatewayConfig_secondaryPrivateIPAddressCount(rName, countStart),
451+
Check: resource.ComposeAggregateTestCheckFunc(
452+
testAccCheckNATGatewayExists(ctx, t, resourceName, &natGateway),
453+
resource.TestCheckResourceAttr(resourceName, "secondary_private_ip_address_count", strconv.Itoa(countStart)),
454+
),
455+
ConfigPlanChecks: resource.ConfigPlanChecks{
456+
PreApply: []plancheck.PlanCheck{
457+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate),
458+
},
459+
},
460+
},
461+
{
462+
Config: testAccVPCNATGatewayConfig_secondaryPrivateIPAddressCount(rName, countUp),
463+
Check: resource.ComposeAggregateTestCheckFunc(
464+
testAccCheckNATGatewayExists(ctx, t, resourceName, &natGateway),
465+
resource.TestCheckResourceAttr(resourceName, "secondary_private_ip_address_count", strconv.Itoa(countUp)),
466+
),
467+
ConfigPlanChecks: resource.ConfigPlanChecks{
468+
PreApply: []plancheck.PlanCheck{
469+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate),
470+
},
471+
},
472+
},
473+
{
474+
Config: testAccVPCNATGatewayConfig_secondaryPrivateIPAddressCount(rName, countDown),
475+
Check: resource.ComposeAggregateTestCheckFunc(
476+
testAccCheckNATGatewayExists(ctx, t, resourceName, &natGateway),
477+
resource.TestCheckResourceAttr(resourceName, "secondary_private_ip_address_count", strconv.Itoa(countDown)),
478+
),
479+
ConfigPlanChecks: resource.ConfigPlanChecks{
480+
PreApply: []plancheck.PlanCheck{
481+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate),
482+
},
483+
},
484+
},
485+
},
486+
})
487+
}
488+
433489
func TestAccVPCNATGateway_secondaryPrivateIPAddresses(t *testing.T) {
434490
ctx := acctest.Context(t)
435491
var natGateway awstypes.NatGateway

internal/service/ec2/wait.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,31 @@ func waitNATGatewayAddressUnassigned(ctx context.Context, conn *ec2.Client, natG
15141514
return nil, err
15151515
}
15161516

1517+
func waitNATGatewaySecondaryPrivateIPAddressCount(ctx context.Context, conn *ec2.Client, natGatewayID string, expectedCount int, timeout time.Duration) (*awstypes.NatGateway, error) {
1518+
stateConf := &retry.StateChangeConf{
1519+
Pending: []string{"pending"},
1520+
Target: []string{"ready"},
1521+
Refresh: statusNATGatewaySecondaryPrivateIPAddressCount(conn, natGatewayID, expectedCount),
1522+
Timeout: timeout,
1523+
ContinuousTargetOccurence: 4,
1524+
}
1525+
1526+
outputRaw, err := stateConf.WaitForStateContext(ctx)
1527+
1528+
if output, ok := outputRaw.(*awstypes.NatGateway); ok {
1529+
for _, natGatewayAddress := range output.NatGatewayAddresses {
1530+
if !aws.ToBool(natGatewayAddress.IsPrimary) && natGatewayAddress.Status == awstypes.NatGatewayAddressStatusFailed {
1531+
retry.SetLastError(err, errors.New(aws.ToString(natGatewayAddress.FailureMessage)))
1532+
break
1533+
}
1534+
}
1535+
1536+
return output, err
1537+
}
1538+
1539+
return nil, err
1540+
}
1541+
15171542
func waitNATGatewayCreated(ctx context.Context, conn *ec2.Client, id string, timeout time.Duration) (*awstypes.NatGateway, error) {
15181543
stateConf := &retry.StateChangeConf{
15191544
Pending: enum.Slice(awstypes.NatGatewayStatePending),

0 commit comments

Comments
 (0)