azureazure-devopsterraformazapi

Terraform add Ip_configuration to already existing NIC which is managed by another module


I have an NIC configured like this

  resource "azurerm_network_interface" "nic" {
  name                 = "nic1"
  location             = "West Europe"
  resource_group_name  = "MyRG"
  enable_ip_forwarding = true

  ip_configuration {
    name                          = "ipconfig1"
    subnet_id                     = azurerm_subnet.publicsubnet.id
    private_ip_address_allocation = "Static"
    private_ip_address            = "10.0.0.10"
    public_ip_address_id          = azurerm_public_ip.ClusterPublicIP.id
  }
}

Which has a public IP assigned and is eventually used by a Firewall in Azure. However, I would like to add multiple IP-Addresses to this NIC. The IP-Addresses should be added, when I create an application. So I am able to create the public IP with:

resource "azurerm_public_ip" "pip" {
  allocation_method   = "Static"
  location            = "West Europe"
  name                = "pip-${var.appname}"
  resource_group_name = var.rgName
}

However, I could not find any resources in the azurerm provider which would allow me to add it to the previously defined nic. The ip_configuration block must be embedded in azurerm_network_interface.

So I tried it with the azapi provider as follows:

resource "azapi_update_resource" "add_pip_to_forti" {
  type        = "Microsoft.Network/networkInterfaces@2023-06-01"
  resource_id = var.nicResourceId

  body = jsonencode({
    properties = {
      ipConfigurations = [
        {
          name = "assignedByTF"
          etag = "W/\"50b5631e-6e90-4196-a2df-d5c280c41b73\""
          type = "Microsoft.Network/networkInterfaces/ipConfigurations"
          properties = {
            privateIPAddress          = "10.0.0.200"
            privateIPAllocationMethod = "Static"
            publicIPAddress = {
              id = azurerm_network_interface.nic.id
            }
            subnet = {
              id = "/subscriptions/my-sub/resourceGroups/my-rg/providers/Microsoft.Network/virtualNetworks/hub-europe/subnets/pubsub"
            }
            primary                 = false
            privateIPAddressVersion = "IPv4"
          }
        }
      ]
    }
  })
}

However, this would delete the existing configuration on the nic, and fails as the primary ip_configuration cannot be deleted.

The question is: (How) Can the block resource "azapi_update_resource" "add_pip_to_forti" to extend the array and keep existing values by using terraform?

Thanks.


Solution

  • While not really a nice solution, it's working. What I ended up doing is the following:

    data "azurerm_network_interface" "nic_before_change" {
      name                 = "nic1"
      resource_group_name  = "MyRG"
    }
    
    resource "null_resource" "assign_ipconfig_to_nic" {
      depends_on = [data.azurerm_network_interface.nic_before_change]
    
      triggers = {
        "subscription_id"                    = "MySub"
        "ResourceGroup"                      = "MyRg"
        "nic_name"                           = "nic1"
        "public_ip_id"                       = azurerm_public_ip.pip.id    "ipconfig_name"                      = local.new_ip_config_name
        "evaluate_and_recreate_if_necessary" = length([for ipconfig in data.azurerm_network_interface.nic_before_change.ip_configuration : ipconfig.private_ip_address if ipconfig.name == local.new_ip_config_name]) == 1 # as this value is false in the first run, but true in all others (except if the resource was modified in the hub), the script must evaluate if the resource needs to be added again or not. Unfortunately, this will show a modification being made to this resource in the first two runs (or actually every two runs after which the ipconfig may have been removed from the nic for whichever reason). However, as the script handles the second run to do nothing, it's not an issue - while still not being nice.
      }
    
      provisioner "local-exec" {
        when    = create
        command = <<EOF
        az account set --subscription ${self.triggers.subscription_id}
        free_ip=$(az network vnet subnet list-available-ips --resource-group rg-hub-europe --vnet-name hub-europe -n publicSubnet --query "[0]" | grep -oP '(?<=\").*(?=\")') # Get free in the subnet
        recreate_resource=$(az network nic ip-config list -g rg-hub-europe --nic-name activeport2 --query "[].name" | grep ${self.triggers.ipconfig_name} -c)
        if [ "$recreate_resource" -eq 0 ] # only (re-)create if a config with the name does not already exist
        then
          az network nic ip-config create -g ${self.triggers.ResourceGroup} -n ${self.triggers.ipconfig_name} --nic-name ${self.triggers.nic_name} --make-primary false --public-ip-address ${self.triggers.public_ip_id} --private-ip-address-version IPv4 --private-ip-address $free_ip
        fi
        EOF
    
      }
    
      # There is a chance the resource was destroyed by other means - don't provoke an error when it's no longer there on resource destruction
      provisioner "local-exec" {
        when    = destroy
        command = <<EOF
        az account set --subscription ${self.triggers.subscription_id}
        resource_still_exists=$(az network nic ip-config list -g rg-hub-europe --nic-name activeport2 --query "[].name" | grep ${self.triggers.ipconfig_name} -c)
        if [ "$resource_still_exists" -eq 1 ]
        then
          az network nic ip-config delete -g ${self.triggers.ResourceGroup} -n ${self.triggers.ipconfig_name} --nic-name ${self.triggers.nic_name}
        fi
        EOF
      }
    }
    
    
    ## thanks to the depends_on, the data is only loaded AFTER the null_resource scripts altered it, so we receive the most up to date information about the resource here.
    data "azurerm_network_interface" "nic_after_change" {
      depends_on = [null_resource.assign_ipconfig_to_nic]
      name                 = "nic1"
      resource_group_name  = "MyRG"
    }
    

    What happens is that I load the current resouce state to nic_before_change, then via a custom script in the null_resource.assign_ipconfig_to_nic analyze if the nic needs to be changed. In my usecase, if the ipconfig must be added for which-ever reason. This resource is also responsible for deleting it upon destruction.

    Eventually, to be able to reference the nics latest state, I import it again to nic_after_change. As this depends on null_resource.assign_ipconfig_tonic, the resource shows the latest state.