Skip to content

Commit 0a53c5f

Browse files
committed
Allows secondary_private_ip_address_count to be updated in place
1 parent b4c0dd3 commit 0a53c5f

5 files changed

Lines changed: 173 additions & 10 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: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1725,6 +1725,33 @@ 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+
secondaryCount++
1744+
}
1745+
}
1746+
1747+
if secondaryCount == expectedCount {
1748+
return output, "ready", nil
1749+
}
1750+
1751+
return output, "pending", nil
1752+
}
1753+
}
1754+
17281755
func statusNATGatewayAttachedAppliances(conn *ec2.Client, id string) retry.StateRefreshFunc {
17291756
return func(ctx context.Context) (any, string, error) {
17301757
output, err := findNATGatewayByID(ctx, conn, id)

internal/service/ec2/vpc_nat_gateway.go

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,72 @@ func resourceNATGatewayUpdate(ctx context.Context, d *schema.ResourceData, meta
474474
}
475475
}
476476
}
477+
478+
if v := d.GetRawConfig().GetAttr("secondary_private_ip_address_count"); v.IsKnown() && !v.IsNull() && d.GetRawConfig().GetAttr("secondary_private_ip_addresses").IsNull() {
479+
desiredCount64, _ := v.AsBigFloat().Int64()
480+
desiredCount := int(desiredCount64)
481+
482+
natGateway, err := findNATGatewayByID(ctx, conn, d.Id())
483+
if err != nil {
484+
return sdkdiag.AppendErrorf(diags, "reading EC2 NAT Gateway (%s): %s", d.Id(), err)
485+
}
486+
487+
var secondaryPrivateIPs []string
488+
for _, natGatewayAddress := range natGateway.NatGatewayAddresses {
489+
if !aws.ToBool(natGatewayAddress.IsPrimary) {
490+
if privateIP := aws.ToString(natGatewayAddress.PrivateIp); privateIP != "" {
491+
secondaryPrivateIPs = append(secondaryPrivateIPs, privateIP)
492+
}
493+
}
494+
}
495+
496+
currentCount := len(secondaryPrivateIPs)
497+
delta := desiredCount - currentCount
498+
499+
if delta > 0 {
500+
input := &ec2.AssignPrivateNatGatewayAddressInput{
501+
NatGatewayId: aws.String(d.Id()),
502+
PrivateIpAddressCount: aws.Int32(int32(delta)),
503+
}
504+
505+
_, err := conn.AssignPrivateNatGatewayAddress(ctx, input)
506+
507+
if err != nil {
508+
return sdkdiag.AppendErrorf(diags, "assigning EC2 NAT Gateway (%s) private IP address count: %s", d.Id(), err)
509+
}
510+
} else if delta < 0 {
511+
removeCount := -delta
512+
513+
if len(secondaryPrivateIPs) < removeCount {
514+
return sdkdiag.AppendErrorf(diags, "cannot unassign %d private IP addresses from EC2 NAT Gateway (%s), only %d secondary addresses are available", removeCount, d.Id(), len(secondaryPrivateIPs))
515+
}
516+
517+
privateIPsToUnassign := secondaryPrivateIPs[:removeCount]
518+
519+
input := &ec2.UnassignPrivateNatGatewayAddressInput{
520+
NatGatewayId: aws.String(d.Id()),
521+
PrivateIpAddresses: privateIPsToUnassign,
522+
}
523+
524+
_, err := conn.UnassignPrivateNatGatewayAddress(ctx, input)
525+
526+
if err != nil {
527+
return sdkdiag.AppendErrorf(diags, "unassigning EC2 NAT Gateway (%s) private IP addresses: %s", d.Id(), err)
528+
}
529+
530+
for _, privateIP := range privateIPsToUnassign {
531+
if _, err := waitNATGatewayAddressUnassigned(ctx, conn, d.Id(), privateIP, d.Timeout(schema.TimeoutUpdate)); err != nil {
532+
return sdkdiag.AppendErrorf(diags, "waiting for EC2 NAT Gateway (%s) private IP address (%s) unassign: %s", d.Id(), privateIP, err)
533+
}
534+
}
535+
}
536+
537+
if delta != 0 {
538+
if _, err := waitNATGatewaySecondaryPrivateIPAddressCount(ctx, conn, d.Id(), desiredCount, d.Timeout(schema.TimeoutUpdate)); err != nil {
539+
return sdkdiag.AppendErrorf(diags, "waiting for EC2 NAT Gateway (%s) secondary private IP address count (%d): %s", d.Id(), desiredCount, err)
540+
}
541+
}
542+
}
477543
case awstypes.ConnectivityTypePublic:
478544
if !d.GetRawConfig().GetAttr("secondary_allocation_ids").IsNull() && d.HasChanges("secondary_allocation_ids") {
479545
o, n := d.GetChange("secondary_allocation_ids")
@@ -654,17 +720,11 @@ func resourceNATGatewayCustomizeDiff(ctx context.Context, diff *schema.ResourceD
654720
return fmt.Errorf(`secondary_allocation_ids is not supported with connectivity_type = "%s"`, connectivityType)
655721
}
656722

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-
}
662-
}
663-
}
664-
665723
if diff.Id() != "" && diff.HasChange("secondary_private_ip_addresses") {
666-
if err := diff.SetNewComputed("secondary_private_ip_address_count"); err != nil {
667-
return fmt.Errorf("setting secondary_private_ip_address_count to Computed: %w", err)
724+
if v := diff.GetRawConfig().GetAttr("secondary_private_ip_addresses"); v.IsKnown() && !v.IsNull() {
725+
if err := diff.SetNewComputed("secondary_private_ip_address_count"); err != nil {
726+
return fmt.Errorf("setting secondary_private_ip_address_count to Computed: %w", err)
727+
}
668728
}
669729
}
670730
case awstypes.ConnectivityTypePublic:

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: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,23 @@ 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+
}
1524+
1525+
outputRaw, err := stateConf.WaitForStateContext(ctx)
1526+
1527+
if output, ok := outputRaw.(*awstypes.NatGateway); ok {
1528+
return output, err
1529+
}
1530+
1531+
return nil, err
1532+
}
1533+
15171534
func waitNATGatewayCreated(ctx context.Context, conn *ec2.Client, id string, timeout time.Duration) (*awstypes.NatGateway, error) {
15181535
stateConf := &retry.StateChangeConf{
15191536
Pending: enum.Slice(awstypes.NatGatewayStatePending),

0 commit comments

Comments
 (0)