Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 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,233 @@
// 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/2023-09-01/publicipprefixes"
"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"
)

var _ sdk.Resource = NATGatewayPublicIpPrefixV6AssociationResource{}

type NATGatewayPublicIpPrefixV6AssociationResource struct{}

type NATGatewayPublicIpPrefixV6AssociationModel struct {
NATGatewayId string `tfschema:"nat_gateway_id"`
PublicIPPrefixId string `tfschema:"public_ip_prefix_id"`
}

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

"public_ip_prefix_id": commonschema.ResourceIDReferenceRequiredForceNew(&publicipprefixes.PublicIPPrefixId{}),
}
}

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

func (NATGatewayPublicIpPrefixV6AssociationResource) ModelObject() interface{} {
return &NATGatewayPublicIpPrefixV6AssociationModel{}
}

func (NATGatewayPublicIpPrefixV6AssociationResource) ResourceType() string {
return "azurerm_nat_gateway_public_ip_prefix_v6_association"
}

func (r NATGatewayPublicIpPrefixV6AssociationResource) 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 NATGatewayPublicIpPrefixV6AssociationModel
if err := metadata.Decode(&state); err != nil {
return err
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
return err
return fmt.Errorf("decoding: %+v", err)

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.

fixed

}

publicIpPrefixId, err := publicipprefixes.ParsePublicIPPrefixID(state.PublicIPPrefixId)
if err != nil {
return err
}

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

locks.ByName(natGatewayId.NatGatewayName, natGatewayResourceName)
defer locks.UnlockByName(natGatewayId.NatGatewayName, natGatewayResourceName)

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)
}

id := commonids.NewCompositeResourceID(natGatewayId, publicIpPrefixId)

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

if publicIPPrefixesV6 := props.PublicIPPrefixesV6; publicIPPrefixesV6 != 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.

publicIPPrefixesV6 is re-assigned here in the if-condition.

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

for _, existingPublicIPPrefix := range *publicIPPrefixesV6 {
if existingPublicIPPrefix.Id == nil {
continue
}

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

publicIpPrefixesV6 = append(publicIpPrefixesV6, existingPublicIPPrefix)
}
}

publicIpPrefixesV6 = append(publicIpPrefixesV6, natgateways.SubResource{
Id: pointer.To(publicIpPrefixId.ID()),
})
props.PublicIPPrefixesV6 = pointer.To(publicIpPrefixesV6)
}
}

if err := client.CreateOrUpdateThenPoll(ctx, *natGatewayId, *natGateway.Model); err != nil {
return fmt.Errorf("updating %s: %+v", *natGatewayId, err)
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
return fmt.Errorf("updating %s: %+v", *natGatewayId, err)
return fmt.Errorf("creating %s: %+v", id, err)

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.

fixed

}

metadata.SetID(id)

return nil
},
}
}

func (NATGatewayPublicIpPrefixV6AssociationResource) 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{}, &publicipprefixes.PublicIPPrefixId{})
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 := NATGatewayPublicIpPrefixV6AssociationModel{
NATGatewayId: id.First.ID(),
PublicIPPrefixId: id.Second.ID(),
}

if model := natGateway.Model; model != nil {
if props := model.Properties; props != nil {
if props.PublicIPPrefixesV6 == nil {
return metadata.MarkAsGone(id)
}

found := false
for _, pipPrefix := range *props.PublicIPPrefixesV6 {
if pipPrefix.Id == nil {
continue
}

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

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

return metadata.Encode(&state)
},
}
}

func (NATGatewayPublicIpPrefixV6AssociationResource) 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{}, &publicipprefixes.PublicIPPrefixId{})
if err != nil {
return err
}

locks.ByName(id.First.NatGatewayName, natGatewayResourceName)
defer locks.UnlockByName(id.First.NatGatewayName, natGatewayResourceName)

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 model := natGateway.Model; model != nil {
if props := model.Properties; props != nil {
publicIpPrefixesV6 := make([]natgateways.SubResource, 0)

if publicIPPrefixesV6 := props.PublicIPPrefixesV6; publicIPPrefixesV6 != 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.

Same as above.

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

for _, publicIPPrefix := range *publicIPPrefixesV6 {
if publicIPPrefix.Id == nil {
continue
}

if !strings.EqualFold(*publicIPPrefix.Id, id.Second.ID()) {
publicIpPrefixesV6 = append(publicIpPrefixesV6, publicIPPrefix)
}
}
}
props.PublicIPPrefixesV6 = pointer.To(publicIpPrefixesV6)
}
}

if err := client.CreateOrUpdateThenPoll(ctx, *id.First, *natGateway.Model); err != nil {
return fmt.Errorf("removing association between %s and %s: %+v", *id.First, *id.Second, err)
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
return fmt.Errorf("removing association between %s and %s: %+v", *id.First, *id.Second, err)
return fmt.Errorf("deleting %s: %+v", id, err)

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.

fixed

}

return nil
},
}
}

func (r NATGatewayPublicIpPrefixV6AssociationResource) IDValidationFunc() pluginsdk.SchemaValidateFunc {
return func(input interface{}, key string) (warnings []string, errors []error) {
_, err := commonids.ParseCompositeResourceID(input.(string), &natgateways.NatGatewayId{}, &publicipprefixes.PublicIPPrefixId{})
if err != nil {
errors = append(errors, err)
}
return warnings, errors
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package network_test

import (
"context"
"fmt"
"strings"
"testing"

"github.com/hashicorp/go-azure-helpers/lang/pointer"
"github.com/hashicorp/go-azure-helpers/resourcemanager/commonids"
"github.com/hashicorp/go-azure-sdk/resource-manager/network/2023-09-01/publicipprefixes"
"github.com/hashicorp/go-azure-sdk/resource-manager/network/2025-01-01/natgateways"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
)

type NatGatewayPublicIpPrefixV6AssociationResource struct{}

func TestAccNatGatewayPublicIpPrefixV6Association_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.

Add a test to verify multiple associations

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

data := acceptance.BuildTestData(t, "azurerm_nat_gateway_public_ip_prefix_v6_association", "test")
r := NatGatewayPublicIpPrefixV6AssociationResource{}
data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccNatGatewayPublicIpPrefixV6Association_requiresImport(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_nat_gateway_public_ip_prefix_v6_association", "test")
r := NatGatewayPublicIpPrefixV6AssociationResource{}
data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.RequiresImportErrorStep(r.requiresImport),
})
}

func (t NatGatewayPublicIpPrefixV6AssociationResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := commonids.ParseCompositeResourceID(state.ID, &natgateways.NatGatewayId{}, &publicipprefixes.PublicIPPrefixId{})
if err != nil {
return nil, err
}

resp, err := clients.Network.NatGateways.Get(ctx, *id.First, natgateways.DefaultGetOperationOptions())
if err != nil {
return nil, fmt.Errorf("retrieving %s: %+v", *id.First, err)
}

found := false
if model := resp.Model; model != nil {
if props := model.Properties; props != nil {
if props.PublicIPPrefixesV6 != nil {
for _, pip := range *props.PublicIPPrefixesV6 {
if pip.Id == nil {
continue
}

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

return pointer.To(found), nil
}

func (r NatGatewayPublicIpPrefixV6AssociationResource) basic(data acceptance.TestData) string {
return fmt.Sprintf(`
%s

provider "azurerm" {
features {}
}

resource "azurerm_nat_gateway" "test" {
name = "acctest-NatGateway-%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
sku_name = "StandardV2"
zones = ["1", "2", "3"]
}

resource "azurerm_nat_gateway_public_ip_prefix_v6_association" "test" {
nat_gateway_id = azurerm_nat_gateway.test.id
public_ip_prefix_id = azurerm_public_ip_prefix.test.id
}
`, r.template(data), data.RandomInteger)
}

func (r NatGatewayPublicIpPrefixV6AssociationResource) requiresImport(data acceptance.TestData) string {
return fmt.Sprintf(`
%s

resource "azurerm_nat_gateway_public_ip_prefix_v6_association" "import" {
nat_gateway_id = azurerm_nat_gateway_public_ip_prefix_v6_association.test.nat_gateway_id
public_ip_prefix_id = azurerm_nat_gateway_public_ip_prefix_v6_association.test.public_ip_prefix_id
}
`, r.basic(data))
}

func (NatGatewayPublicIpPrefixV6AssociationResource) template(data acceptance.TestData) string {
return fmt.Sprintf(`
resource "azurerm_resource_group" "test" {
name = "acctestRG-ngpipv6-%d"
location = "%s"
}

resource "azurerm_public_ip_prefix" "test" {
name = "acctest-pipPrefixV6-%d"
location = azurerm_resource_group.test.location
resource_group_name = azurerm_resource_group.test.name
ip_version = "IPv6"
prefix_length = 127
sku = "StandardV2"
}
`, data.RandomInteger, data.Locations.Primary, data.RandomInteger)
}
Loading
Loading