Skip to content

New Resource: azurerm_nat_gateway_public_ip_v6_association#31321

Open
neil-yechenwei wants to merge 23 commits intohashicorp:mainfrom
neil-yechenwei:natgatewaypublicipv6association
Open

New Resource: azurerm_nat_gateway_public_ip_v6_association#31321
neil-yechenwei wants to merge 23 commits intohashicorp:mainfrom
neil-yechenwei:natgatewaypublicipv6association

Conversation

@neil-yechenwei
Copy link
Copy Markdown
Contributor

@neil-yechenwei neil-yechenwei commented Dec 9, 2025

Community Note

  • Please vote on this PR by adding a 👍 reaction to the original PR to help the community and maintainers prioritize for review
  • Please do not leave comments along the lines of "+1", "me too" or "any updates", they generate extra noise for PR followers and do not help prioritize for review

Description

This PR is to support new association resource azurerm_nat_gateway_public_ip_v6_association for new feature publicIpAddressesV6. This PR depends on #31197. Once PR 31197 is merged, please don't merge this PR since I will rebase the code.

PR Checklist

  • I have followed the guidelines in our Contributing Documentation.
  • I have checked to ensure there aren't other open Pull Requests for the same update/change.
  • I have checked if my changes close any open issues. If so please include appropriate closing keywords below.
  • I have updated/added Documentation as required written in a helpful and kind way to assist users that may be unfamiliar with the resource / data source.
  • I have used a meaningful PR title to help maintainers and other users understand this change and help prevent duplicate work.
    For example: “resource_name_here - description of change e.g. adding property new_property_name_here

Changes to existing Resource / Data Source

  • I have added an explanation of what my changes do and why I'd like you to include them (This may be covered by linking to an issue above, but may benefit from additional explanation).
  • I have written new tests for my resource or datasource changes & updated any relevant documentation.
  • I have successfully run tests with my changes locally. If not, please provide details on testing challenges that prevented you running the tests.
  • (For changes that include a state migration only). I have manually tested the migration path between relevant versions of the provider.

Testing

  • My submission includes Test coverage as described in the Contribution Guide and the tests pass. (if this is not possible for any reason, please include details of why you did or could not add test coverage)
--- PASS: TestAccNatGatewayPublicIpV6Association_basic (213.37s)
--- PASS: TestAccNatGatewayPublicIpV6Association_requiresImport (189.84s)

Change Log

Below please provide what should go into the changelog (if anything) conforming to the Changelog Format documented here.

  • New Resource: azurerm_nat_gateway_public_ip_v6_association

This is a (please select all that apply):

  • Bug Fix
  • New Feature (ie adding a service, resource, or data source)
  • Enhancement
  • Breaking Change

Related Issue(s)

Fixes # 0000

AI Assistance Disclosure

  • AI Assisted - This contribution was made by, or with the assistance of, AI/LLMs

Rollback Plan

If a change needs to be reverted, we will publish an updated version of the provider.

Changes to Security Controls

Are there any changes to security controls (access controls, encryption, logging) in this pull request? If so, explain.

Note

If this PR changes meaningfully during the course of review please update the title and description as required.

Copy link
Copy Markdown
Collaborator

@teowa teowa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @neil-yechenwei, thank you for submitting the PR. I’ve added some comments inline. Also, the PR might need to be rebased onto the main branch once #31197 is merged.

Comment on lines +32 to +44
"nat_gateway_id": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: natgateways.ValidateNatGatewayID,
},

"public_ip_address_id": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: commonids.ValidatePublicIPAddressID,
},
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"nat_gateway_id": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: natgateways.ValidateNatGatewayID,
},
"public_ip_address_id": {
Type: pluginsdk.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: commonids.ValidatePublicIPAddressID,
},
"nat_gateway_id": commonschema.ResourceIDReferenceRequiredForceNew(&natgateways.NatGatewayId{}),
"public_ip_address_id": commonschema.ResourceIDReferenceRequiredForceNew(&commonids.PublicIPAddressId{}),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

natGateway, err := client.Get(ctx, *natGatewayId, natgateways.DefaultGetOperationOptions())
if err != nil {
if response.WasNotFound(natGateway.HttpResponse) {
return fmt.Errorf("%s was not found", natGatewayId)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pointers used in fmt.Errorf() should be dereferenced, please fix similar issues in the _resource file.

Suggested change
return fmt.Errorf("%s was not found", natGatewayId)
return fmt.Errorf("%s was not found", *natGatewayId)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

@neil-yechenwei
Copy link
Copy Markdown
Contributor Author

@teowa , thanks for the comments. I updated PR. Please take another review. Below is the latest test result.

--- PASS: TestAccNatGatewayPublicIpV6Association_basic (202.84s)
--- PASS: TestAccNatGatewayPublicIpV6Association_requiresImport (196.31s)

@teowa
Copy link
Copy Markdown
Collaborator

teowa commented Dec 9, 2025

Thanks @neil-yechenwei , LGTM!

return err
}

locks.ByName(natGatewayId.NatGatewayName, natGatewayResourceName)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to lock by the nat gateway's ID? Will there be two gateways in different resource group but same name?

Copy link
Copy Markdown
Contributor Author

@neil-yechenwei neil-yechenwei Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. API allows to create two gateways with same name in different resource groups.

if props := model.Properties; props != nil {
publicIpAddressesV6 := make([]natgateways.SubResource, 0)

if publicIPAddressesV6 := props.PublicIPAddressesV6; publicIPAddressesV6 != nil {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

publicIPAddressesV6 is assigned (using :=) to props.PublicIPAddressesV6 in this if-condition, the modifications to publicIPAddressesV6 stays in the if-condition. Consider using another variable name?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated


type NatGatewayPublicIpV6AssociationResource struct{}

func TestAccNatGatewayPublicIpV6Association_basic(t *testing.T) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add another test to show verify if multiple associations can work together.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

@teowa
Copy link
Copy Markdown
Collaborator

teowa commented Apr 8, 2026

Hi @ms-zhenhua , thanks for reviewing this, I have updated the code, please kindly take another look.

Copy link
Copy Markdown
Collaborator

@ms-zhenhua ms-zhenhua left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM~

@WodansSon WodansSon marked this pull request as ready for review April 8, 2026 08:32
@teowa
Copy link
Copy Markdown
Collaborator

teowa commented Apr 8, 2026

image

Copy link
Copy Markdown
Collaborator

@wuxu92 wuxu92 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates on this PR! I left some comments while it looks good overall. we are good to go once you addressed these comments.

"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
)

type NatGatewayPublicIpV6AssociationResource struct{}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should use IPv6 or ipv6, althought the SDK generated with IPAddressV6, the standard spelling is a lowercased v6 as IPv6. we should rename all other words in this PR too.

Suggested change
type NatGatewayPublicIpV6AssociationResource struct{}
type NatGatewayPublicIPv6AssociationResource struct{}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

Comment on lines +147 to +170
if model := natGateway.Model; model != nil {
if props := model.Properties; props != nil {
if props.PublicIPAddressesV6 == nil {
return metadata.MarkAsGone(id)
}

publicIPAddressFound := false
for _, pip := range *props.PublicIPAddressesV6 {
if pip.Id == nil {
continue
}

if strings.EqualFold(*pip.Id, id.Second.ID()) {
publicIPAddressFound = true
break
}
}

if !publicIPAddressFound {
return metadata.MarkAsGone(id)
}
}
}

Copy link
Copy Markdown
Collaborator

@wuxu92 wuxu92 Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to handle if model or props is nil as well, and we can make it clear:

Suggested change
if model := natGateway.Model; model != nil {
if props := model.Properties; props != nil {
if props.PublicIPAddressesV6 == nil {
return metadata.MarkAsGone(id)
}
publicIPAddressFound := false
for _, pip := range *props.PublicIPAddressesV6 {
if pip.Id == nil {
continue
}
if strings.EqualFold(*pip.Id, id.Second.ID()) {
publicIPAddressFound = true
break
}
}
if !publicIPAddressFound {
return metadata.MarkAsGone(id)
}
}
}
if natGateway.Model == nil || natGateway.Model.Properties == nil {
return fmt.Errorf("retrieving %s: `model` or `properties` was nil", id)
}
publicIPAddressFound := false
for _, pip := range pointer.From(model.Properties.PublicIPAddressesV6) {
if strings.EqualFold(pointer.From(pip.Id), id.Second.ID()) {
publicIPAddressFound = true
break
}
}
if !publicIPAddressFound {
return metadata.MarkAsGone(id)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

return map[string]*pluginsdk.Schema{
"nat_gateway_id": commonschema.ResourceIDReferenceRequiredForceNew(&natgateways.NatGatewayId{}),

"public_ip_address_id": commonschema.ResourceIDReferenceRequiredForceNew(&commonids.PublicIPAddressId{}),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a validator in CustomizeDiff to ensure the ID is an IPv6 address, assuming the Get API is a lightweight call? That way we’ll catch the error during the plan stage if an IPv4 ID is supplied.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

Comment on lines +72 to +73
locks.ByID(natGatewayId.ID())
defer locks.UnlockByID(natGatewayId.ID())
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to update the locks of azurerm_nat_gateway_public_ip_prefix_association and azurerm_nat_gateway_public_ip_association to use ID instead locks.ByName if we use ID here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

}

func (r NatGatewayPublicIpV6AssociationResource) ResourceType() string {
return "azurerm_nat_gateway_public_ip_v6_association"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be no breaks in ipv6?

Suggested change
return "azurerm_nat_gateway_public_ip_v6_association"
return "azurerm_nat_gateway_public_ipv6_association"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

Comment on lines +92 to +111
publicIpAddressesV6 := make([]natgateways.SubResource, 0)

if v := natGateway.Model.Properties.PublicIPAddressesV6; v != nil {
for _, existingPublicIPAddress := range *v {
if existingPublicIPAddress.Id == nil {
continue
}

if strings.EqualFold(*existingPublicIPAddress.Id, publicIpAddressId.ID()) {
return metadata.ResourceRequiresImport(r.ResourceType(), id)
}

publicIpAddressesV6 = append(publicIpAddressesV6, existingPublicIPAddress)
}
}

publicIpAddressesV6 = append(publicIpAddressesV6, natgateways.SubResource{
Id: pointer.To(state.PublicIpAddressId),
})
natGateway.Model.Properties.PublicIPAddressesV6 = pointer.To(publicIpAddressesV6)
Copy link
Copy Markdown
Collaborator

@wuxu92 wuxu92 Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems we don't need to copy the list to publicIpAddressesV6?

Suggested change
publicIpAddressesV6 := make([]natgateways.SubResource, 0)
if v := natGateway.Model.Properties.PublicIPAddressesV6; v != nil {
for _, existingPublicIPAddress := range *v {
if existingPublicIPAddress.Id == nil {
continue
}
if strings.EqualFold(*existingPublicIPAddress.Id, publicIpAddressId.ID()) {
return metadata.ResourceRequiresImport(r.ResourceType(), id)
}
publicIpAddressesV6 = append(publicIpAddressesV6, existingPublicIPAddress)
}
}
publicIpAddressesV6 = append(publicIpAddressesV6, natgateways.SubResource{
Id: pointer.To(state.PublicIpAddressId),
})
natGateway.Model.Properties.PublicIPAddressesV6 = pointer.To(publicIpAddressesV6)
existingIPs := pointer.From(natGateway.Model.Properties.PublicIPAddressesV6)
for _, existingPublicIPAddress := range existingIPs {
if strings.EqualFold(pointer.From(existingPublicIPAddress.Id), publicIpAddressId.ID()) {
return metadata.ResourceRequiresImport(r.ResourceType(), id)
}
}
publicIPAddressesV6 := append(existingIPs, natgateways.SubResource{
Id: pointer.To(state.PublicIpAddressId),
})
natGateway.Model.Properties.PublicIPAddressesV6 = pointer.To(publicIPAddressesV6)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

Comment on lines +207 to +217
if v := natGateway.Model.Properties.PublicIPAddressesV6; v != nil {
for _, publicIPAddress := range *v {
if publicIPAddress.Id == nil {
continue
}

if !strings.EqualFold(*publicIPAddress.Id, id.Second.ID()) {
publicIpAddressesV6 = append(publicIpAddressesV6, publicIPAddress)
}
}
}
Copy link
Copy Markdown
Collaborator

@wuxu92 wuxu92 Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if v := natGateway.Model.Properties.PublicIPAddressesV6; v != nil {
for _, publicIPAddress := range *v {
if publicIPAddress.Id == nil {
continue
}
if !strings.EqualFold(*publicIPAddress.Id, id.Second.ID()) {
publicIpAddressesV6 = append(publicIpAddressesV6, publicIPAddress)
}
}
}
need2Delete := false
for _, publicIPAddress := range pointer.From(natGateway.Model.Properties.PublicIPAddressesV6) {
if !strings.EqualFold(pointer.From(publicIPAddress.Id), id.Second.ID()) {
publicIpAddressesV6 = append(publicIpAddressesV6, publicIPAddress)
} else {
need2Delete = true
}
}
// if addresss already not exists in gateway, then we don't need to call the update
if !need2Delete {
// add optional logging here
return nil
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

@teowa
Copy link
Copy Markdown
Collaborator

teowa commented Apr 9, 2026

Hi @wuxu92 , thanks for reviewing this! I've updated the code, please take another look.

Copy link
Copy Markdown
Collaborator

@wuxu92 wuxu92 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @teowa for the updates. This is good enough to me. I just noticed that the public_ip_address_id usually references to a public ip resource's id field, which means it is unknown in plan stage but still need to the apply stage to known to value. but the CustomizeDiff still has value if the public ip resource is under provision already. I approved this PR and wait HC's review on this.

also please update the PR title to the new resource name.

image

type NatGatewayPublicIPv6AssociationResource struct{}

var (
_ sdk.Resource = NatGatewayPublicIPv6AssociationResource{}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_ sdk.Resource = NatGatewayPublicIPv6AssociationResource{}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

@sreallymatt
Copy link
Copy Markdown
Collaborator

sreallymatt commented Apr 13, 2026

Is being discussed internally with @wuxu92

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants