How do I loop through te below given variable in terraform - azure

I am trying to create vnets for multiple environments using one single variable. I am not quite sure if it is possible. My variable is given below
azure_vnets = {
Prod = [
{
cidr = "10.10.0.0/24"
vnet_name = "prod-vnet1"
dns = "10.10.0.1"
rg = "prodrg1"
location = "eastus"
},
{
cidr = "10.10.1.0/24"
vnet_name = "prod-vnet2"
dns = "10.10.0.2"
rg = "prodrg2"
location = "eastus"
}
],
nonProd = [
{
cidr = "10.10.0.0/24"
vnet_name = "nonprod-vnet1"
dns = "10.10.0.1"
rg = "nonprodrg1"
location = "eastus"
},
{
cidr = "10.10.1.0/24"
vnet_name = "nonprod-vnet2"
dns = "10.10.0.2"
rg = "nonprodrg2"
location = "eastus"
}
]
}
So as to create multiple vnets from this
resource "azurerm_virtual_network" "this" {
for_each = xxx
name = each.xxx
xxxx
xxx
}

You have to flatten it first:
locals {
flat_azure_vnets = merge([
for env_name, env_vn_list in var.azure_vnets:
{
for idx, env_vn in env_vn_list:
"${env_name}-${idx}" => env_vn
}
]...)
}
then you use it:
resource "azurerm_virtual_network" "this" {
for_each = local.flat_azure_vnets
name = each.value["vnet_name"]
xxxx
xxx
}

Related

How to create multiple VMs in multiple Subnets using terraform modules

I'm new to terraform and trying to create a module for multiple vms and within multiple subnets within multiple regions.
I have 2 regions, in each region i have 3 subnets and within each subnet I'm creating 2 vms.
I'm able to create multiple subnets within the 2 regions using the module but I need help in creating multiple vms within these multiple subnets created. Do you have any idea how to do this?
Below is my code for the main.tf as well as terraform.tfvars
terraform.tfvars
# subnets
subnet = {
"Subnet1" = {
name = "subnet1"
address_prefixes = [""]
enforce_private_link_endpoint_network_policies =
},
"Subnet2" = {
name = "subnet2"
address_prefixes = [""]
enforce_private_link_endpoint_network_policies =
},
"Subnet3" = {
name = "subnet3"
address_prefixes = [""]
enforce_private_link_endpoint_network_policies =
},
}
# vms
vm = {
"vm1" = {
name = "vm1"
size = ""
admin_username = ""
zone = ""
vtpm_enabled = true
nic = {
name = ""
ip_name = ""
private_ip_address = ""
}
},
"vm2" = {
name = "vm2"
size = ""
admin_username = ""
zone = ""
vtpm_enabled = true
nic = {
name = ""
ip_name = ""
private_ip_address = ""
}
},
main.tf
module "subnet" {
source = "./../modules/subnet"
depends_on = [module.vnet]
for_each = var.region.subnet
# Resource group
network_rg_name = var.region.network_rg_name
location = var.region.location
# Route table
route_table_id = module.route_table.route_table_id
# Network
subnet = each.value
vnet = var.region.vnet
}
module "vm" {
source = "./../modules/vm"
for_each = var.region.vm
# Resource group
vm_rg_name = var.region.vm_rg_name
location = var.region.location
# Subnets
vm_subnet_id = module.subnet["Subnet1"].subnet_id
# Virtual Machine
vm = each.value
vm_shared = var.tfvars.vm_shared
vm_pwd = var.vm_pwd
}
This may work
In terraform.tfvars edit the vms, adding the name of you required subnet
terraform.tfvars
...
# vms
vm = {
"vm1" = {
name = "vm1"
size = ""
admin_username = ""
zone = ""
subnet = "subnet1" ### require subnet
vtpm_enabled = true
nic = {
name = ""
ip_name = ""
private_ip_address = ""
}
},
"vm2" = {
name = "vm2"
size = ""
admin_username = ""
zone = ""
subnet = "subnet2" ### require subnet
vtpm_enabled = true
nic = {
name = ""
ip_name = ""
private_ip_address = ""
}
},
In your main.tf add a condition and a "locals"
main.tf
locals {
subnet1 = module.Subnet["Subnet1"].subnet_id
subnet2 = module.Subnet["Subnet2"].subnet_id
subnet3 = module.Subnet["Subnet3"].subnet_id
}
module "vm" {
source = "./../modules/vm"
for_each = var.region.vm
# Resource group
vm_rg_name = var.region.vm_rg_name
location = var.region.location
# Subnets
vm_subnet_id = "${each.value.subnet}" == "subnet1" ? "${local.subnet1}" : "${each.value.subnet}" == "subnet2" ? "${local.subnet2}" : "${each.value.subnet}" == "subnet3" ? "${local.subnet3}" :""
# Virtual Machine
vm = each.value
vm_shared = var.tfvars.vm_shared
vm_pwd = var.vm_pwd
}
Hope this helps!

Multiple Vnets and subnets using terraform modules

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.
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]
}

Using multiple module outputs to a new module in Terraform

I have seen some codes with same intention but somehow I couldnt make it work.
I have two different modules,
subnet - Where I'm creating two subnets where subnet name is provided in tfvars
nsg - Where I'm creating two nsg where nsg name is provided in tfvars
And I output the created subnet id's and nsg_ids to my main.tf from both module
What I'm trying to do is to associate each subnets to each nsg's like
subnet1 to nsg1
subnet2 to nsg2
Main.tf
module "nsg" {
source = "./Modules/NSGConfig"
nsglist = var.nsglist
resource_group_name = azurerm_resource_group.resource_group.name
location = azurerm_resource_group.resource_group.location
nsg = tomap(
{
for k, subnet_id in module.SUBNETS.subnet_ids : k =>
{
subnet_id = subnet_id
}
}
)
}
NSG.tf (only including association part)
resource "azurerm_subnet_network_security_group_association" "nsg_association" {
for_each=var.nsg
subnet_id = each.value.subnet_id
network_security_group_id = azurerm_network_security_group.nsg[*].nsg_id #wont work
}
variable.tf (NSG module)
variable "nsg" {
type = map(object({
subnet_id = string
}))
}
I tried to nest the for (in main.tf) to include the output from nsgid but failed.
Ps. I'm really new to terraform
Main.tfvars
RGlocation = "westus"
RGname = "TEST-RG1-TERRAFORM"
VNETname = "TEST-VNET-TERRAFORM"
address_space = "10.0.0.0/16"
Subnetlist = {
"s1" = { name = "TESTSUBNET1-TERRAFORM", address = "10.0.1.0/24" },
"s2" = { name = "TESTSUBNET2-TERRAFORM", address = "10.0.2.0/24" },
"s3" = { name = "TESTSUBNET3-TERRAFORM", address = "10.0.3.0/24" }
}
niclist = {
"s1" = { name = "TESTNIC1-TERRAFORM" },
"s2" = { name = "TESTNIC2-TERRAFORM" },
"s3" = { name = "TESTNIC3-TERRAFORM" }
}
nsglist = {
"s1" = { name = "TESTNSG1-TERRAFORM" },
"s2" = { name = "TESTNSG1-TERRAFORM" },
"s3" = { name = "TESTNSG1-TERRAFORM" }
}
--- Update 2
Module output from the subnet module and NSG module is as below
Outputs:
nsg_id = tomap({
"s1" = "./resourceGroups/TEST-RG1-TERRAFORM/providers/Microsoft.Network/networkSecurityGroups/TESTNSG1-TERRAFORM"
"s2" = "./resourceGroups/TEST-RG1-TERRAFORM/providers/Microsoft.Network/networkSecurityGroups/TESTNSG1-TERRAFORM"
"s3" = "./resourceGroups/TEST-RG1-TERRAFORM/providers/Microsoft.Network/networkSecurityGroups/TESTNSG1-TERRAFORM"
})
sub_id = tomap({
"s1" = "./resourceGroups/TEST-RG1-TERRAFORM/providers/Microsoft.Network/virtualNetworks/SACHIN-TEST-VNET-TERRAFORM/subnets/TESTSUBNET1-TERRAFORM"
"s2" = "./resourceGroups/TEST-RG1-TERRAFORM/providers/Microsoft.Network/virtualNetworks/SACHIN-TEST-VNET-TERRAFORM/subnets/TESTSUBNET2-TERRAFORM"
"s3" = "./resourceGroups/TEST-RG1-TERRAFORM/providers/Microsoft.Network/virtualNetworks/SACHIN-TEST-VNET-TERRAFORM/subnets/TESTSUBNET3-TERRAFORM"
})

How to pass values from one's module list to another module with for_each

I have a module that creates vnets and subnets (module vnet):
resource "azurerm_virtual_network" "xxx" {
count = length(var.vnets)
name = var.vnets[count.index].vnet_name
address_space = var.vnets[count.index].address_space
location = var.resource_location
resource_group_name = var.resource_group_name
dynamic "subnet" {
for_each = var.vnets[count.index].subnets
content {
name = subnet.value.name
address_prefix = subnet.value.address
security_group = azurerm_network_security_group.xxx.id
}
}
tags = var.tags
}
var.vnets is
vnets = [
{
vnet_name = "01-vnet"
address_space = ["10.0.0.0/23"]
subnets = [
{
name = "workloads-01"
address = "10.0.0.0/24"
}
]
},
{
vnet_name = "02-vnet"
address_space = ["10.0.2.0/23"]
subnets = [
{
name = "workloads-02"
address = "10.0.2.0/24"
}
]
}
]
And then I pass subnets to my root file
output "subnets" {
value = flatten(azurerm_virtual_network.xxx[*].subnet[*].id)
}
Resulted output
Outputs:
subnets = [
"/subscriptions/XXX/resourceGroups/EIS/providers/Microsoft.Network/virtualNetworks/eis-01-vnet/subnets/eis-workloads-01",
"/subscriptions/XXX/resourceGroups/EIS/providers/Microsoft.Network/virtualNetworks/eis-02-vnet/subnets/eis-workloads-02",
]
Now I want to use those subnets when I create VMs in another module (vm module):
resource "azurerm_network_interface" "nics" {
for_each = var.vms
name = "${each.value.name}-nic"
location = var.resource_location
resource_group_name = var.resource_group_name
ip_configuration {
name = "iface-${each.value.name}"
subnet_id = element(var.subnet_id, count.index)
private_ip_address_allocation = "Dynamic"
}
tags = var.tags
}
Where var.vms
vms = {
vm1 = {
name = "vm1"
vm_size = "Standard_B1ms"
username = "centos"
password = "222!"
disk_size = "30"
sku = "7_9"
ext_ip = "1"
},
vm2 = {
name = "vm2"
vm_size = "Standard_B1ls"
username = "centos"
password = "111!"
disk_size = "35"
sku = "7_9"
ext_ip = ""
}
}
And var.subnet_id is just, defined in vm module.
variable "subnet_id" {}
My root's main.tf
module "vm" {
source = "./modules/vm"
for_each = var.vms
vms = var.vms
publicip_name = var.publicip_name
subnet_id = module.vnet.subnets[*]
resource_group_name = azurerm_resource_group.xxx.name
resource_location = azurerm_resource_group.xxx.location
tags = local.common_tags
}
How can I pass subnet_ids from module vnet to module vm, so the resource "azurerm_network_interface" will use one subnet_ids for each key defined in vms?
I would use "element(var.subnet_id, count.index)" for the count, but how can I pass those subnet ids for a resource that will be created with for_each?
You can get iterate over your var.vms with index as follows:
resource "azurerm_network_interface" "nics" {
for_each = {for idx, vm in keys(var.vms): idx => var.vms[vm]}
name = "${each.value.name}-nic"
location = var.resource_location
resource_group_name = var.resource_group_name
ip_configuration {
name = "eis-iface-${each.value.name}"
subnet_id = element(var.subnet_id, each.key)
private_ip_address_allocation = "Dynamic"
}
tags = var.tags
}
Note: keys returns the map keys in alphabetical order if that meters.

Terraform: How to iterate the name or another variable in a resource group when the variable being passed in is a list of a list of maps?

This is what I have so far, while I can pull a specific list or name using the local variables, I am having trouble transitioning this into the resource group. First, am I attempting this the right way? If not how can I iterate on the name for the subnet so that the subnets belonging to the respective vnet map are added?
variable "vnets" {
default = [
{
vnet_name = "test-vnet"
address_space = "10.250.0.0"
network_size = 16
subnets = [
{
name = "first-subnet"
network_security_group = "first-nsg"
security_group_rules = [
{
name = "first-sg"
priority = 100
}
]
},
{
name = "second-subnet"
network_security_group = "second-nsg"
security_group_rules = [
{
name = "second-sg"
priority = 100
}
]
}
]
}
]
}
locals {
subnet_names = {
for vnet in var.vnets[*]:
(vnet.vnet_name) => vnet.subnets[*].name
}
security_group_names = flatten(var.vnets[*].subnets[*].security_group_rules[*].name)
}
resource "azurerm_subnet" "subnets" {
count = length(var.vnets)
#??? name = locals.subnet_names[count.index].subnets.name
resource_group_name = data.azurerm_resource_group.network_group.name
virtual_network_name = azurerm_virtual_network.vnets.*.name
address_prefixes = ["10.0.1.0/24"]
}
I think the easiest would be to flatten your subnet_names:
locals {
subnet_names = {
for vnet in var.vnets[*]:
(vnet.vnet_name) => vnet.subnets[*].name
}
security_group_names = flatten(var.vnets[*].subnets[*].security_group_rules[*].name)
# uniqueness of "${vnet}-${subnet}" pairs is assumed. it will not work
# if the pairs are not unique
subnet_names_flat = merge([
for vnet, subnets in local.subnet_names:
{
for subnet in subnets:
"${vnet}-${subnet}" => {name = vnet, subnet = subnet}
}
]...)
}
Which will result in subnet_names_flat being:
{
"test-vnet-first-subnet" = {
"name" = "test-vnet"
"subnet" = "first-subnet"
}
"test-vnet-second-subnet" = {
"name" = "test-vnet"
"subnet" = "second-subnet"
}
}
Then your azurerm_subnet.subnets could as below. However, I'm not able to verify correctness of your the azurerm_subnet, thus you may need to change it further. But the idea is to iterate over local.subnet_names_flat, which makes the for_each very easy to use:
resource "azurerm_subnet" "subnets" {
for_each = local.subnet_names_flat
name = each.value.subnet
resource_group_name = data.azurerm_resource_group.network_group.name
virtual_network_name = each.value.vnet
address_prefixes = ["10.0.1.0/24"]
}

Resources