Reputation: 75
I am new to terraform and I am trying to create module for multiple vnets and under that multiple subnets
I am able to create multiple vnets using the module but I am facing issue in creating multiple subnets on one or more of the vnets created.
Below is my code for the module and as well as main.tf
network resources module/main.tf
data "azurerm_resource_group" "network" {
name = var.resource_group_name
}
resource "azurerm_virtual_network" "vnets" {
count = length(var.vnet_names)
name = var.vnet_names[count.index]
resource_group_name = data.azurerm_resource_group.network.name
location = var.vnet_location != null ? var.vnet_location : data.azurerm_resource_group.network.location
address_space = [var.vnet_adress_spaces[count.index]]
}
network resources module/variables.tf
variable "vnet_names" {
description = "Name of the vnets to be created"
type = list(string)
default = ["vnet1","vnet2","vnet3"]
}
variable "vnet_adress_spaces" {
description = "Name of the vnets to be created"
type = list(string)
default = ["192.168.0.0/16" ,"10.0.0.0/16","10.80.0.0/16"]
}
variable "resource_group_name" {
description = "Name of the resource group to be imported."
type = string
}
variable "vnet_location" {
description = "The location of the vnet to create. Defaults to the location of the resource group."
type = string
default = null
}
variable "subnet_names" {
description = "The list of subnets which needs to be created"
type = list(list(string))
default = [[],["subnet1_vnet1","subnet2_vnet1"],["subnet1_vnet3","subnet2_vnet3","subnet3_vnet3"]]
}
variable "subnet_addresses" {
description = "The list of subnets which needs to be created"
type = list(list(string))
default = [[],["10.0.2.0/24","10.0.0.0/24"],["10.80.2.0/24","10.80.1.0/24","10.80.0.0/24"]]
}
main.tf
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "=2.98.0"
}
}
}
# Configure the Microsoft Azure Provider
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "rg1" {
name = "rg1"
location = "West Europe"
}
module network {
source = "./network_resources"
resource_group_name = azurerm_resource_group.rg1.name
}
3 vnets are successfully created but I am facing issue in writing subnet creation as mentioned in variables.tf Please can you help me with that or suggest a better way of implementing this
Output.tf of module
output "vnet_names" {
description = "The name of the virtual networks"
value = {for k, v in azurerm_virtual_network.vnets: k => v.name}
}
output "vnet_addresses" {
description = "The name of the virtual networks"
value = {for k, v in azurerm_virtual_network.vnets: k => v.address_space}
}
output "subnet_names" {
description = "The name of the subnets"
value = {for k, v in azurerm_subnet.subnets: k => v.name}
}
output "subnet_addresses" {
description = "The name of the subnet addresses"
value = {for k, v in azurerm_subnet.subnets: k => v.address_prefixes}
}
output "subnet_ids" {
description = "The name of the subnet addresses"
value = {for k, v in azurerm_subnet.subnets: k => v.id}
}
When I am taking the subnet value same for two vnets as per the updated variables.tf
vnets = {
"mel-dev-identity-vnet01" = {
address_space = "10.0.0.0/16",
subnets = [
{
subnet_name = "subnet-mel-AD-dev"
subnet_address = "10.0.2.0/24"
service_endpoints = []
},
{
subnet_name = "subnet-mel-okt-dev"
subnet_address = "10.0.0.0/24"
service_endpoints = []
},
{
subnet_name = "GatewaySubnet"
subnet_address = "10.0.0.0/26"
service_endpoints = []
},
]
},
"mel-dev-identity-vnet02" = {
address_space = "10.80.0.0/16"
subnets = [
{
subnet_name = "subnet-syd-AD-dev"
subnet_address = "10.80.2.0/24"
service_endpoints = []
},
{
subnet_name = "subnet-syd-okt-dev"
subnet_address = "10.80.1.0/24"
service_endpoints = []
},
{
subnet_name = "GatewaySubnet"
subnet_address = "10.80.0.0/26"
service_endpoints = []
},
]
}
}
I am getting the below error:
│ Error: Duplicate object key
│
│ on network_resources\locals.tf line 11, in locals:
│ 11: subnets = { for subnet in local.subnets_flatlist : subnet.subnet_name => subnet }
│ ├────────────────
│ │ subnet.subnet_name is "GatewaySubnet"
│
│ Two different items produced the key "GatewaySubnet" in this 'for' expression. If duplicates are expected, use the
│ ellipsis (...) after the value expression to enable grouping by key.
Upvotes: 2
Views: 3355
Reputation: 89
This ended up working.
vnet_id
module.network.vnet_id[keys(var.vnets)[0]]
vnet_name
module.network.vnet_name[keys(var.vnets)[0]]
Upvotes: 0
Reputation: 762
I see two issues in your code:
You store attributes (e.g. subnet_name, subnet_addressed) of the same resources in different variables of type list or nested list. This way you have to ensure consistency across the different variables manually which can become tedious and error prone. Imagine you want to add a third subnet to vnet bupavnet2. You have to make sure that you insert the new name and subnet at the corresponding positions in two nested lists.
BTW: Is it intended that the subnets in vnet bupavnet2 are named subnet#-bupavnet1 ? See what I mean ??? :)
In order to create multiple subnets in multiple vnets dynamically, you would need a nested loop which iterates over the vnets in the outer loop and across the subnets in the inner loop. Terraform, however, does not support nested for_each loops on resource level.
One way to overcome the first issue is to use complex types like objects or maps in order to make the relationship between the attributes of a resource explicit. This way it would be easy to spot the naming issue of the subnets in bupavnet2. If you want to add an additional subnet, you just need to add an additional object to the subnets list.
variable "vnets" {
type = map(object({
address_space = string
subnets = list(object({
subnet_name = string
subnet_address = string
}))
}))
default = {
"bupavnet1" = {
address_space = "192.168.0.0/16",
subnets = []
},
"bupavnet2" = {
address_space = "10.0.0.0/16",
subnets = [
{
subnet_name = "subnet1_bupavnet1"
subnet_address = "10.0.2.0/24"
},
{
subnet_name = "subnet2_bupavnet1"
subnet_address = "10.0.0.0/24"
}
]
},
"bupavnet3" = {
address_space = "10.80.0.0/16"
subnets = [
{
subnet_name = "subnet1_bupavnet3"
subnet_address = "10.80.2.0/24"
},
{
subnet_name = "subnet2_bupavnet3"
subnet_address = "10.80.1.0/24"
},
{
subnet_name = "subnet3_bupavnet3"
subnet_address = "10.80.0.0/24"
},
]
}
}
}
In succession, the creation of the vnets would change to
resource "azurerm_virtual_network" "vnets" {
for_each = var.vnets
name = each.key
resource_group_name = data.azurerm_resource_group.network.name
location = var.vnet_location != null ? var.vnet_location : data.azurerm_resource_group.network.location
address_space = [each.value.address_space]
}
Now let's have a look how to tackle the nested for loop issue. In Terraform you would address this by flattening the nested structure which we defined above. In the first step we create a flat list of objects representing the subnets to be created. So our variable subnets_flatlist
has the type tuple of objects.
Unfortunately the for_each
argument in Terraform requires the type map or set of strings. So we need a second step to create a map out of our flat list.
locals {
subnets_flatlist = flatten([for key, val in var.vnets : [
for subnet in val.subnets : {
vnet_name = key
subnet_name = subnet.subnet_name
subnet_address = subnet.subnet_address
}
]
])
subnets = { for subnet in local.subnets_flatlist : subnet.subnet_name => subnet }
}
Once we created our helper structures we can iterate over the subnets map and create the subnet resources:
resource "azurerm_subnet" "subnets" {
for_each = local.subnets
name = each.value.subnet_name
resource_group_name = data.azurerm_resource_group.network.name
virtual_network_name = azurerm_virtual_network.vnets[each.value.vnet_name].name
address_prefixes = [each.value.subnet_address]
}
Upvotes: 2