Skip to content

Commit f5821f6

Browse files
committed
Allows secondary_private_ip_address_count to be updated in place
1 parent ff7613a commit f5821f6

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
@@ -1741,6 +1741,39 @@ func statusNATGatewayAddressByNATGatewayIDAndPrivateIP(conn *ec2.Client, natGate
17411741
}
17421742
}
17431743

1744+
func statusNATGatewaySecondaryPrivateIPAddressCount(conn *ec2.Client, natGatewayID string, expectedCount int) retry.StateRefreshFunc {
1745+
return func(ctx context.Context) (any, string, error) {
1746+
output, err := findNATGatewayByID(ctx, conn, natGatewayID)
1747+
1748+
if retry.NotFound(err) {
1749+
return nil, "", nil
1750+
}
1751+
1752+
if err != nil {
1753+
return nil, "", err
1754+
}
1755+
1756+
secondaryCount := 0
1757+
for _, natGatewayAddress := range output.NatGatewayAddresses {
1758+
if !aws.ToBool(natGatewayAddress.IsPrimary) && aws.ToString(natGatewayAddress.PrivateIp) != "" {
1759+
if natGatewayAddress.Status == awstypes.NatGatewayAddressStatusFailed {
1760+
return output, string(natGatewayAddress.Status), nil
1761+
}
1762+
1763+
if natGatewayAddress.Status == awstypes.NatGatewayAddressStatusSucceeded {
1764+
secondaryCount++
1765+
}
1766+
}
1767+
}
1768+
1769+
if secondaryCount == expectedCount {
1770+
return output, "ready", nil
1771+
}
1772+
1773+
return output, "pending", nil
1774+
}
1775+
}
1776+
17441777
func statusNATGatewayAttachedAppliances(conn *ec2.Client, id string) retry.StateRefreshFunc {
17451778
return func(ctx context.Context) (any, string, error) {
17461779
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
@@ -434,7 +434,12 @@ func resourceNATGatewayUpdate(ctx context.Context, d *schema.ResourceData, meta
434434
case awstypes.AvailabilityModeZonal:
435435
switch awstypes.ConnectivityType(d.Get("connectivity_type").(string)) {
436436
case awstypes.ConnectivityTypePrivate:
437-
if d.HasChanges("secondary_private_ip_addresses") {
437+
countRaw := d.GetRawConfig().GetAttr("secondary_private_ip_address_count")
438+
countConfigured := countRaw.IsKnown() && !countRaw.IsNull()
439+
addressesRaw := d.GetRawConfig().GetAttr("secondary_private_ip_addresses")
440+
addressesConfigured := addressesRaw.IsKnown() && !addressesRaw.IsNull()
441+
442+
if addressesConfigured && d.HasChange("secondary_private_ip_addresses") {
438443
o, n := d.GetChange("secondary_private_ip_addresses")
439444
os, ns := o.(*schema.Set), n.(*schema.Set)
440445

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

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

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

internal/service/ec2/vpc_nat_gateway_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,62 @@ func TestAccVPCNATGateway_secondaryPrivateIPAddressCountToSpecific(t *testing.T)
438438
})
439439
}
440440

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

internal/service/ec2/wait.go

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

1557+
func waitNATGatewaySecondaryPrivateIPAddressCount(ctx context.Context, conn *ec2.Client, natGatewayID string, expectedCount int, timeout time.Duration) (*awstypes.NatGateway, error) {
1558+
stateConf := &retry.StateChangeConf{
1559+
Pending: []string{"pending"},
1560+
Target: []string{"ready"},
1561+
Refresh: statusNATGatewaySecondaryPrivateIPAddressCount(conn, natGatewayID, expectedCount),
1562+
Timeout: timeout,
1563+
ContinuousTargetOccurence: 4,
1564+
}
1565+
1566+
outputRaw, err := stateConf.WaitForStateContext(ctx)
1567+
1568+
if output, ok := outputRaw.(*awstypes.NatGateway); ok {
1569+
for _, natGatewayAddress := range output.NatGatewayAddresses {
1570+
if !aws.ToBool(natGatewayAddress.IsPrimary) && natGatewayAddress.Status == awstypes.NatGatewayAddressStatusFailed {
1571+
retry.SetLastError(err, errors.New(aws.ToString(natGatewayAddress.FailureMessage)))
1572+
break
1573+
}
1574+
}
1575+
1576+
return output, err
1577+
}
1578+
1579+
return nil, err
1580+
}
1581+
15571582
func waitNATGatewayCreated(ctx context.Context, conn *ec2.Client, id string, timeout time.Duration) (*awstypes.NatGateway, error) {
15581583
stateConf := &retry.StateChangeConf{
15591584
Pending: enum.Slice(awstypes.NatGatewayStatePending),

0 commit comments

Comments
 (0)