How to create Azure vnets and subnets, then return the IDs and access them in code

I’ve written code to create azure vnets with subnets by passing in a list of vnet objects which also contain the subnets. The problem is that I’m not sure how to return the vnet ids and the subnet ids, then after the code finishes, be able to access the ID of say a subnet by the subnet’s name, such as variable.subnet[“Subnet Name”].id.

Here’s what I have right now and I’m sure I’ll need to change it:

main.tf:

resource "azurerm_virtual_network" "vnet" {
  count               = length(var.vnet_settings)
  name                = var.vnet_settings[count.index].name
  address_space       = var.vnet_settings[count.index].address_space
  location            = var.vnet_settings[count.index].location
  resource_group_name = var.vnet_settings[count.index].resource_group

  dynamic "subnet" {
    for_each = var.vnet_settings[count.index].subnets
    content {
      name           = subnet.value.name
      address_prefix = subnet.value.prefix    
    }
  }
}

Variables.tf:

variable "vnet_settings" {
  description = "A list of objects containing settings for virtual networks and their subnets"
  type = list(object({
    name          = string
    address_space = list(string)
    location      = string
    resource_group = string
    subnets       = list(object({
      name   = string
      prefix = string
    }))
  }))
}

In my root main.tf, it’s called like this:

module "virtual_networks" {
  source = "./modules/vnet"

  vnet_settings = var.vnet_settings

  depends_on = [ module.resource_groups ]
  
}

So after that module executes, I want to be able to use the subnet names and vnet names, which are in my tfvars file, and get the IDs. I’ll have to have the IDs so that I can create virtual machines and other resources that need the IDs.

Thanks!

Hi,

So typically, when using a module to create resources, the module will also define a set of ‘outputs’ which you can then refer to in the calling/root module.

It’s not 100% clear, but it looks like the main.tf you show is the module main.tf (./modules/vnet/main.tf)

What you need to do as part of your module is define some outputs to return useful attributes. By convention these would be in an outputs.tf file and might look like this for a module deploying a single VNet and all of its subnets:

./modules/vnet/outputs.tf:

output "id" {
value = azurerm_virtual_network.vnet.id
description = "Vnet id"
}

output "subnets" {
  value = azurerm_virtual_network.vnet.subnet
  description = "The subnets within the virtual network"
}

In the calling/root module you would then refer to these as follows:

resource "azurerm_network_interface" "example" {
  name                = "example-nic"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = module.vnet.subnets[index].id
    private_ip_address_allocation = "Dynamic"
  }
}

However, you specifically ask to be able to refer to the subnets using the subnet name - a ‘key’ rather than an ‘index’.
In your current implementation (defining the subnets inline with the virtual_network resource) this is not really possible. As the subnets are created by your dynamic block and therefore an array/list of the subnets rather than a map.

My suggestion here is to look at changing your code to use the stand-alone subnet resource. Check here the note blocks on the virtual network resource in the documentation here: : azurerm_virtual_network | Resources | hashicorp/azurerm | Terraform | Terraform Registry
Ans also provide the subnet details as a map of objects rather than a list of objects for the following reasons:

  1. You will be able to remove the dynamic block from your vnet resource. Dynamic blocks can make it difficult to read, understand and maintain your code so the advice is to use them only when necessary
  2. You will be able to refer to subnets using a string/name as the ‘key’, rather than just a number.

So you will remove your dynamic block from your VNET resource and add a new
azurerm_subnet resource that might look something like this:

resource "azurerm_subnet" "subnets" {
  for_each = var.vnet_settings.subnets
  name                 =  each.key
  resource_group_name  = var.vnet_settings.resource_group_name
  virtual_network_name = azurerm.vnet_settings.name
  address_prefixes     = each.value.address_prefixes
}

The variable part would be as follows (assuming the key you use for the map will be the name of the subnet):

subnets = map(object({
   prefix = string
}))

So set similar to:

subnets = {
   "myvmsubnet" = {
      prefix = "10.0.0.0/24"
   }
   "mystoragesubnet" = {
      prefix = "10.0.10.0/24"
   }
}

These can then be output:

output "subnets" {
  value = azurerm_virtual_network.subnets
  description = "The subnets within the virtual network"
}

And referenced:

module.vnet.subnets["{key}"].id

such as:

module.vnet.subnets["mystoragesubnet"].id

Note that the above is not a drop-in solution for your module, but should guide you to what you need to do.

Some other general points:

  • I notice you are using the ‘count’ meta argument a lot in your code. Take a look at the for_each argument (refer to the docs for both count and for_each). In your use case (and many others with the same approach) you are at risk of destroying and recreating resources as they are based upon an index, starting from 0, rather than key. If for some reason order of the list is changed (eg. adding or removing a subnet anywhere other than at the end of the list), terraform will force replacement (destroy and recreate) of all resources of which the index in the list has changed. This can cause some big issues!
    Take a look at this article which goes into count vs. for_each and risks in more detail than I have: Terraform — for_each vs count… Count | by Jacek Kikiewicz | Medium

  • I expect you are creating this module for your own education and learning? If not, then consider looking at the terraform registry for already published modules that already do what you need for well defined elements such as this. They can also be reviewed to help you in learning terraform and understanding how to write a module for the resources - Here is the most popular module for Azure VNET : Azure/vnet/azurerm | Terraform Registry - Note that this module also transitioned from using count to for_each when it became available.

  • Lastly - when posting code snippets try and use the preformatted text format and other formatting available in the forum as it makes your post easier to read and follow and aids in properly formatting the code (with highlighting)

Hope that helps.

Happy Terraforming!