Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package network

import (
"context"
"fmt"
"strings"
"time"

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-helpers/lang/response"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonids"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema"
"github.com/hashicorp/go-azure-sdk/resource-manager/network/2025-01-01/natgateways"
"github.com/hashicorp/terraform-provider-azurerm/internal/locks"
"github.com/hashicorp/terraform-provider-azurerm/internal/sdk"
"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


var _ sdk.Resource = NatGatewayPublicIpV6AssociationResource{}

type NatGatewayPublicIpV6AssociationModel struct {
NatGatewayId string `tfschema:"nat_gateway_id"`
PublicIpAddressId string `tfschema:"public_ip_address_id"`
}

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

"public_ip_address_id": commonschema.ResourceIDReferenceRequiredForceNew(&commonids.PublicIPAddressId{}),
}
}

func (r NatGatewayPublicIpV6AssociationResource) Attributes() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{}
}

func (r NatGatewayPublicIpV6AssociationResource) ModelObject() interface{} {
return &NatGatewayPublicIpV6AssociationModel{}
}

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

}

func (r NatGatewayPublicIpV6AssociationResource) Create() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 30 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.Network.NatGateways

var state NatGatewayPublicIpV6AssociationModel
if err := metadata.Decode(&state); err != nil {
return fmt.Errorf("decoding: %+v", err)
}

publicIpAddressId, err := commonids.ParsePublicIPAddressID(state.PublicIpAddressId)
if err != nil {
return err
}

natGatewayId, err := natgateways.ParseNatGatewayID(state.NatGatewayId)
if err != nil {
return err
}

locks.ByID(natGatewayId.ID())
defer locks.UnlockByID(natGatewayId.ID())

natGateway, err := client.Get(ctx, *natGatewayId, natgateways.DefaultGetOperationOptions())
if err != nil {
if response.WasNotFound(natGateway.HttpResponse) {
return fmt.Errorf("%s was not found", *natGatewayId)
}
return fmt.Errorf("retrieving %s: %+v", *natGatewayId, err)
}

if natGateway.Model == nil {
return fmt.Errorf("retrieving %s: `model` was nil", *natGatewayId)
}
if natGateway.Model.Properties == nil {
return fmt.Errorf("retrieving %s: `properties` was nil", *natGatewayId)
}

id := commonids.NewCompositeResourceID(natGatewayId, publicIpAddressId)

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


if err := client.CreateOrUpdateThenPoll(ctx, *natGatewayId, *natGateway.Model); err != nil {
return fmt.Errorf("creating %s: %+v", id, err)
}

metadata.SetID(id)
return nil
},
}
}

func (r NatGatewayPublicIpV6AssociationResource) Read() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 5 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.Network.NatGateways

id, err := commonids.ParseCompositeResourceID(metadata.ResourceData.Id(), &natgateways.NatGatewayId{}, &commonids.PublicIPAddressId{})
if err != nil {
return err
}

natGateway, err := client.Get(ctx, *id.First, natgateways.DefaultGetOperationOptions())
if err != nil {
if response.WasNotFound(natGateway.HttpResponse) {
return metadata.MarkAsGone(id)
}
return fmt.Errorf("retrieving %s: %+v", *id.First, err)
}

state := NatGatewayPublicIpV6AssociationModel{
NatGatewayId: id.First.ID(),
PublicIpAddressId: id.Second.ID(),
}

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 metadata.Encode(&state)
},
}
}

func (r NatGatewayPublicIpV6AssociationResource) Delete() sdk.ResourceFunc {
return sdk.ResourceFunc{
Timeout: 30 * time.Minute,
Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
client := metadata.Client.Network.NatGateways

id, err := commonids.ParseCompositeResourceID(metadata.ResourceData.Id(), &natgateways.NatGatewayId{}, &commonids.PublicIPAddressId{})
if err != nil {
return err
}

locks.ByID(id.First.ID())
defer locks.UnlockByID(id.First.ID())

natGateway, err := client.Get(ctx, *id.First, natgateways.DefaultGetOperationOptions())
if err != nil {
if response.WasNotFound(natGateway.HttpResponse) {
return fmt.Errorf("%s was not found", *id.First)
}
return fmt.Errorf("retrieving %s: %+v", *id.First, err)
}

if natGateway.Model == nil {
return fmt.Errorf("retrieving %s: `model` was nil", *id.First)
}
if natGateway.Model.Properties == nil {
return fmt.Errorf("retrieving %s: `properties` was nil", *id.First)
}

publicIpAddressesV6 := make([]natgateways.SubResource, 0)

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

natGateway.Model.Properties.PublicIPAddressesV6 = pointer.To(publicIpAddressesV6)

if err := client.CreateOrUpdateThenPoll(ctx, *id.First, *natGateway.Model); err != nil {
return fmt.Errorf("deleting %s: %+v", id, err)
}

return nil
},
}
}

func (r NatGatewayPublicIpV6AssociationResource) IDValidationFunc() pluginsdk.SchemaValidateFunc {
return func(input interface{}, key string) (warnings []string, errors []error) {
if _, err := commonids.ParseCompositeResourceID(input.(string), &natgateways.NatGatewayId{}, &commonids.PublicIPAddressId{}); err != nil {
errors = append(errors, err)
}
return warnings, errors
}
}
Loading
Loading