Skip to content

Commit

Permalink
r/nat_gateway_public_ip_association: using a custom ID / support for …
Browse files Browse the repository at this point in the history
…delta updates in `azurerm_nat_gateway` (hashicorp#7106)

This PR fixes some issues spotted in hashicorp#6450 - namely:

Using a custom Resource ID for the azurerm_nat_gateway_public_ip_association resource - which includes the Public IP Address ID so that we can determine which Public IP Address is being referenced
Supporting delta updates in the azurerm_nat_gateway resource
  • Loading branch information
tombuildsstuff authored and pbrit committed May 31, 2020
1 parent eb77262 commit b516a90
Show file tree
Hide file tree
Showing 8 changed files with 363 additions and 167 deletions.
Expand Up @@ -3,11 +3,11 @@ package network
import (
"fmt"
"log"
"strings"
"time"

"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-03-01/network"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks"
Expand All @@ -18,14 +18,14 @@ import (
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

func resourceArmNatGatewayPublicIpAssociation() *schema.Resource {
func resourceArmNATGatewayPublicIpAssociation() *schema.Resource {
return &schema.Resource{
Create: resourceArmNatGatewayPublicIpAssociationCreate,
Read: resourceArmNatGatewayPublicIpAssociationRead,
Delete: resourceArmNatGatewayPublicIpAssociationDelete,
Create: resourceArmNATGatewayPublicIpAssociationCreate,
Read: resourceArmNATGatewayPublicIpAssociationRead,
Delete: resourceArmNATGatewayPublicIpAssociationDelete,

Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error {
_, err := parse.NatGatewayID(id)
_, err := parse.NatGatewayPublicIPAddressAssociationID(id)
return err
}),

Expand All @@ -47,18 +47,18 @@ func resourceArmNatGatewayPublicIpAssociation() *schema.Resource {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: azure.ValidateResourceID,
ValidateFunc: validate.PublicIPAddressID,
},
},
}
}

func resourceArmNatGatewayPublicIpAssociationCreate(d *schema.ResourceData, meta interface{}) error {
func resourceArmNATGatewayPublicIpAssociationCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Network.NatGatewayClient
ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d)
defer cancel()

log.Printf("[INFO] preparing arguments for Nat Gateway <-> Public Ip Association creation.")
log.Printf("[INFO] preparing arguments for NAT Gateway <-> Public IP Association creation.")
natGatewayId := d.Get("nat_gateway_id").(string)
publicIpAddressId := d.Get("public_ip_address_id").(string)
parsedNatGatewayId, err := parse.NatGatewayID(natGatewayId)
Expand All @@ -72,21 +72,24 @@ func resourceArmNatGatewayPublicIpAssociationCreate(d *schema.ResourceData, meta
natGateway, err := client.Get(ctx, parsedNatGatewayId.ResourceGroup, parsedNatGatewayId.Name, "")
if err != nil {
if utils.ResponseWasNotFound(natGateway.Response) {
return fmt.Errorf("Nat Gateway %q (Resource Group %q) was not found.", parsedNatGatewayId.Name, parsedNatGatewayId.ResourceGroup)
return fmt.Errorf("NAT Gateway %q (Resource Group %q) was not found.", parsedNatGatewayId.Name, parsedNatGatewayId.ResourceGroup)
}
return fmt.Errorf("failed to retrieve Nat Gateway %q (Resource Group %q): %+v", parsedNatGatewayId.Name, parsedNatGatewayId.ResourceGroup, err)
return fmt.Errorf("failed to retrieve NAT Gateway %q (Resource Group %q): %+v", parsedNatGatewayId.Name, parsedNatGatewayId.ResourceGroup, err)
}

id := fmt.Sprintf("%s|%s", *natGateway.ID, publicIpAddressId)
publicIpAddresses := make([]network.SubResource, 0)
if natGateway.PublicIPAddresses != nil {
for _, existingPublicIPAddress := range *natGateway.PublicIPAddresses {
if existingPublicIPAddress.ID != nil {
if *existingPublicIPAddress.ID == publicIpAddressId {
return tf.ImportAsExistsError("azurerm_nat_gateway_public_ip_association", *natGateway.ID)
}
if existingPublicIPAddress.ID == nil {
continue
}

publicIpAddresses = append(publicIpAddresses, existingPublicIPAddress)
if strings.EqualFold(*existingPublicIPAddress.ID, publicIpAddressId) {
return tf.ImportAsExistsError("azurerm_nat_gateway_public_ip_association", id)
}

publicIpAddresses = append(publicIpAddresses, existingPublicIPAddress)
}
}

Expand All @@ -97,98 +100,119 @@ func resourceArmNatGatewayPublicIpAssociationCreate(d *schema.ResourceData, meta

future, err := client.CreateOrUpdate(ctx, parsedNatGatewayId.ResourceGroup, parsedNatGatewayId.Name, natGateway)
if err != nil {
return fmt.Errorf("failed to update Public IP Association for Nat Gateway %q (Resource Group %q): %+v", parsedNatGatewayId.Name, parsedNatGatewayId.ResourceGroup, err)
return fmt.Errorf("failed to update Public IP Association for NAT Gateway %q (Resource Group %q): %+v", parsedNatGatewayId.Name, parsedNatGatewayId.ResourceGroup, err)
}

if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("failed to wait for completion of Public IP Association for Nat Gateway %q (Resource Group %q): %+v", parsedNatGatewayId.Name, parsedNatGatewayId.ResourceGroup, err)
return fmt.Errorf("failed to wait for completion of Public IP Association for NAT Gateway %q (Resource Group %q): %+v", parsedNatGatewayId.Name, parsedNatGatewayId.ResourceGroup, err)
}

resp, err := client.Get(ctx, parsedNatGatewayId.ResourceGroup, parsedNatGatewayId.Name, "")
if err != nil {
return fmt.Errorf("failed to retrieve Nat Gateway %q (Resource Group %q): %+v", parsedNatGatewayId.Name, parsedNatGatewayId.ResourceGroup, err)
}
d.SetId(*resp.ID)
d.SetId(id)

return resourceArmNatGatewayPublicIpAssociationRead(d, meta)
return resourceArmNATGatewayPublicIpAssociationRead(d, meta)
}

func resourceArmNatGatewayPublicIpAssociationRead(d *schema.ResourceData, meta interface{}) error {
func resourceArmNATGatewayPublicIpAssociationRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Network.NatGatewayClient
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.NatGatewayID(d.Id())
id, err := parse.NatGatewayPublicIPAddressAssociationID(d.Id())
if err != nil {
return err
}

natGateway, err := client.Get(ctx, id.ResourceGroup, id.Name, "")
natGateway, err := client.Get(ctx, id.NatGateway.ResourceGroup, id.NatGateway.Name, "")
if err != nil {
if utils.ResponseWasNotFound(natGateway.Response) {
log.Printf("[DEBUG] Nat Gateway %q (Resource Group %q) could not be found - removing from state!", id.Name, id.ResourceGroup)
log.Printf("[DEBUG] NAT Gateway %q (Resource Group %q) could not be found - removing from state!", id.NatGateway.Name, id.NatGateway.ResourceGroup)
d.SetId("")
return nil
}
return fmt.Errorf("failed to retrieve Nat Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
return fmt.Errorf("failed to retrieve NAT Gateway %q (Resource Group %q): %+v", id.NatGateway.Name, id.NatGateway.ResourceGroup, err)
}

if natGateway.NatGatewayPropertiesFormat == nil {
return fmt.Errorf("`properties` was nil for NAT Gateway %q (Resource Group %q)", id.NatGateway.Name, id.NatGateway.ResourceGroup)
}
props := *natGateway.NatGatewayPropertiesFormat

if props.PublicIPAddresses == nil {
log.Printf("[DEBUG] NAT Gateway %q (Resource Group %q) doesn't have any Public IP's - removing from state!", id.NatGateway.Name, id.NatGateway.ResourceGroup)
d.SetId("")
return nil
}

publicIPAddressId := ""
for _, pip := range *props.PublicIPAddresses {
if pip.ID == nil {
continue
}

if strings.EqualFold(*pip.ID, id.PublicIPAddressID) {
publicIPAddressId = *pip.ID
break
}
}

if natGateway.PublicIPAddresses == nil {
log.Printf("[DEBUG] Nat Gateway %q (Resource Group %q) doesn't have a Public IP - removing from state!", id.Name, id.ResourceGroup)
if publicIPAddressId == "" {
log.Printf("[DEBUG] Association between NAT Gateway %q (Resource Group %q) and Public IP Address %q was not found - removing from state", id.NatGateway.Name, id.NatGateway.ResourceGroup, id.PublicIPAddressID)
d.SetId("")
return nil
}

d.Set("nat_gateway_id", natGateway.ID)
d.Set("public_ip_address_id", publicIPAddressId)

return nil
}

func resourceArmNatGatewayPublicIpAssociationDelete(d *schema.ResourceData, meta interface{}) error {
func resourceArmNATGatewayPublicIpAssociationDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Network.NatGatewayClient
ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d)
defer cancel()

id, err := parse.NatGatewayID(d.Id())
id, err := parse.NatGatewayPublicIPAddressAssociationID(d.Id())
if err != nil {
return err
}

publicIpAddressId := d.Get("public_ip_address_id").(string)

locks.ByName(id.Name, natGatewayResourceName)
defer locks.UnlockByName(id.Name, natGatewayResourceName)
locks.ByName(id.NatGateway.Name, natGatewayResourceName)
defer locks.UnlockByName(id.NatGateway.Name, natGatewayResourceName)

natGateway, err := client.Get(ctx, id.ResourceGroup, id.Name, "")
natGateway, err := client.Get(ctx, id.NatGateway.ResourceGroup, id.NatGateway.Name, "")
if err != nil {
if utils.ResponseWasNotFound(natGateway.Response) {
return fmt.Errorf("Nat Gateway %q (Resource Group %q) was not found.", id.Name, id.ResourceGroup)
return fmt.Errorf("NAT Gateway %q (Resource Group %q) was not found", id.NatGateway.Name, id.NatGateway.ResourceGroup)
}

return fmt.Errorf("failed to retrieve Nat Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
return fmt.Errorf("retrieving NAT Gateway %q (Resource Group %q): %+v", id.NatGateway.Name, id.NatGateway.ResourceGroup, err)
}
if natGateway.NatGatewayPropertiesFormat == nil {
return fmt.Errorf("retrieving NAT Gateway %q (Resource Group %q): `properties` was nil", id.NatGateway.Name, id.NatGateway.ResourceGroup)
}

publicIpAddresses := make([]network.SubResource, 0)
if publicIPAddresses := natGateway.PublicIPAddresses; publicIPAddresses != nil {
if publicIPAddresses := natGateway.NatGatewayPropertiesFormat.PublicIPAddresses; publicIPAddresses != nil {
for _, publicIPAddress := range *publicIPAddresses {
if publicIPAddress.ID == nil {
continue
}

if *publicIPAddress.ID != publicIpAddressId {
if !strings.EqualFold(*publicIPAddress.ID, id.PublicIPAddressID) {
publicIpAddresses = append(publicIpAddresses, publicIPAddress)
}
}
}
natGateway.PublicIPAddresses = &publicIpAddresses
natGateway.NatGatewayPropertiesFormat.PublicIPAddresses = &publicIpAddresses

future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, natGateway)
future, err := client.CreateOrUpdate(ctx, id.NatGateway.ResourceGroup, id.NatGateway.Name, natGateway)
if err != nil {
return fmt.Errorf("failed to remove Public Ip Association for Nat Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
return fmt.Errorf("removing association between NAT Gateway %q (Resource Group %q) and Public IP Address %q: %+v", id.NatGateway.Name, id.NatGateway.ResourceGroup, id.PublicIPAddressID, err)
}

if err = future.WaitForCompletionRef(ctx, client.Client); err != nil {
return fmt.Errorf("failed to wait for removal of Public Ip Association for Nat Gateway %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err)
return fmt.Errorf("waiting for association between Public IP ID %q for NAT Gateway %q (Resource Group %q) to be removed: %+v", id.PublicIPAddressID, id.NatGateway.Name, id.NatGateway.ResourceGroup, err)
}

return nil
Expand Down

0 comments on commit b516a90

Please sign in to comment.