Multiple For Each in Terraform - azure

I am trying to deploy multiple for_each in terraform. I have two variables:
variable "VirtualNetworks" is for two VLANS, deployed in two different locations.
variable "VirtualMachines" is for six VM's, 3 in each of the VLANS.
Following are variable types:
variable "VirtualNetworks" {
type = list(object({
VirtualNetworksName = string
VirtualNetworksLocation = string
SubnetName = string
NGSName = string
}))...}
variable "VirtualMachines" {
type = list(object({
OS = string
location = string
hostname = string
interfaceid = string
size = string
environment = string
publisher = string
offer = string
sku = string
version = string
}))...}
The issue is when I am trying to assign 6 network interfaces dynamically, I don't know how to. Following is the way I have tried.
resource "azurerm_network_interface" "tf_example_nic" {
for_each = { for i, v in var.VirtualMachines : i => v }
# for_each = { for j, w in var.VirtualNetworks : j => w }
name = each.value.interfaceid
location = each.value.location
resource_group_name = azurerm_resource_group.tf_example_rg.name
ip_configuration {
name = "internal"
subnet_id = each.key != "0||1||2" ? azurerm_subnet.tf_example_subnet[0].id : azurerm_subnet.tf_example_subnet[1].id
# subnet_id = azurerm_subnet.tf_example_subnet[each.key].id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.tf_example_public_ip[each.key].id
}
}
Is there a way I can try and make this dynamic. Any assistance will be really helpful.

Related

Terraform nested dynamic blocks

I'm trying to deploy an Azure application gateway in Terraform, in particular I need to create a nested dynamic blocks.
I have tried to implement this (this part of the code is in a file called application_gateway.tf):
dynamic "url_path_map" {
for_each = var.path_maps
content {
name = outer_block.value["name"]
default_backend_address_pool_name = outer_block.value["backend"]
default_backend_http_settings_name = outer_block.value["backend_set"]
dynamic "url_path_rule" {
for_each = url_path_map.value["upm"]
content{
name = url_path_rule.value["name_rule"]
paths = url_path_rule.value["path"]
backend_address_pool_name = url_path_rule.value["backend"]
backend_http_settings_name = url_path_rule.value["backend_set"]
}
}
}
}
The correspective variables.tf file is:
variable "path_maps" {
default = []
type = list(object({
name = string
backend = string
backend_set = string
upm = list(object({
name_rule = string
path = string
backend = string
backend_set = string
}))
}))
}
With the following module call (this part of the script is in another file called main.tf):
module "application_gateway" {
source = "../modules/resources-hub/application_gateway"
resource_group_name = module.resource_group.name
resource_group_location = module.resource_group.location
subnet_id = module.agw_subnet.id
public_ip_address_id = module.app_gw_pip.id
firewall_policy_id = module.agw_web_application_firewall.id
log_analytics_workspace_id = module.log_analytics_workspace.id
path_maps = [{name = "dev_url_path_name", backend = "devBackend", backend_set = "devHttpSetting", name_rule = "dev_path_rule_name_app", path = "/app/*"},
{name = "tst_url_path_name", backend = "tstBackend", backend_set = "tstHttpSetting", name_rule = "dev_path_rule_name_edp", path = "/edp/*"},
{name = "uat_url_path_name", backend = "uatBackend", backend_set = "uatHttpSetting", name_rule = "dev_path_rule_name_internal", path = "/internal/*"}]
}
At the end, what I would like to obtain is this but using the nested dynamic blocks:
url_path_map {
name = "dev_url_path_name"
default_backend_address_pool_name = "devBackend"
default_backend_http_settings_name = "devHttpSetting"
path_rule {
name = "dev_path_rule_name_app_edp"
paths = ["/app/*"]
backend_address_pool_name = "devBackend"
backend_http_settings_name = "devHttpSetting"
}
path_rule {
name = "dev_path_rule_name_internal"
paths = ["/edp/*"]
backend_address_pool_name = "devBackend"
backend_http_settings_name = "devHttpSetting"
}
path_rule {
name = "dev_path_rule_name_internal"
paths = ["/internal/*"]
backend_address_pool_name = "sinkPool"
backend_http_settings_name = "devHttpSetting"
}
}
This is the error that I get if I run "terraform validate":
enter image description here
Thank you in advance!
I have tried the code above but I got the error in the image.
The first problem is on the definition of the variable "path_maps", because is different as the path_maps format that you are passing to the module.
You can modify the path_maps before passing to the module with the correct format, or you can change variable to fit the format that you define.
thats why you are getting the error that "upm" is required

Construct a object list from multiple instances of a terraform module

I'm trying to figure out how to turn multiple module instances into a map of objects. I have a map of "Customer". I then want the public IP of each of the created customers to build a list of firewall rules. The Customer module has many outputs including "name" and "public_ip_address" The AzureSQLFirewall module expects a map of objects each attributes "name" and "ipAddress".
Main Module:
module "Customer"{
for_each = var.customers
source = "./customer"
customerName = "${each.value["name"]}"
}
module "AzureSQLFirewall" {
source = "./azureSQLFirewall"
azureSQLServerID = module.BaseInfrastructure.sql_server_id
azureSQLAllowedIPs = { for v in module.Customer:
{
name = v.name,
ipAddress = v.public_ip
}
}
}
AzureSQLFirewall.tf:
terraform {
required_version = ">= 0.14.9"
}
resource "azurerm_mssql_firewall_rule" "azureSQLFirewall" {
for_each = var.azureSQLAllowedIPs
name = each.value.name
server_id = var.azureSQLServerID
start_ip_address = each.value.ipAddress
end_ip_address = each.value.ipAddress
}
variable "azureSQLServerID"{
type = string
}
variable "azureSQLAllowedIPs" {
type = map(object({
name = string
ipAddress = string
}))
}
EDIT: Turns out, I was quite close:
azureSQLAllowedIPs = { for k,v in module.Customer: k => {
name = v.customerName,
ipAddress = v.public_ip}
}

Azure Subnet NSG Association using Terraform

I am trying to using Terraform to automate my VNet setup. this includes setting up the subnet and nsg association. Below partial exerts of the code.
CODE SAMPLE:
====== locals.tf ==================
locals {
subnets = {
private = var.allow_sub
public = var.notallow_sub
admin = var.admin_sub
}
}
====== variables.tf ====================
variable "allow_sub" {
description = "private"
type = object({
name = string
address_prefixes = list(string)
network_security_group = string
route_table = string
})
}
variable "notallow_sub" {
description = "public"
type = object({
name = string
address_prefixes = list(string)
network_security_group = string
route_table = string
})
}
variable "admin_sub" {
description = "management"
type = object({
name = string
address_prefixes = list(string)
network_security_group = string
route_table = string
})
}
==== input.tfvar ==============
notallow_sub = {
name = "test1"
address_prefixes = ["10.100.1.0/24"]
network_security_group = "testnsg1"
route_table = "testrt3"
}
allow_sub = {
name = "test2"
address_prefixes = ["10.100.2.16/28"]
network_security_group = "testnsg2"
route_table = "testrt2"
}
admin_sub = {
name = "test3"
address_prefixes = ["10.100.3.0/28"]
network_security_group = "testnsg3"
route_table = "testrt21"
}
=== main.tf ====
resource "azurerm_subnet" "mysubnet" {
for_each = var.subnets
name = each.key
resource_group_name = var.rg_name
virtual_network_name = var.vnet_name
address_prefixes = each.value.address_prefixes
}
.
.
.
resource "azurerm_subnet_network_security_group_association" "this" {
for_each = { for k, v in local.subnets : k => v if lookup(v, "network_security_group", "") != "" }
subnet_id = azurerm_subnet[each.value].id
network_security_group_id = azurerm_network_security_group[each.value].network_security_group.id
}
resource "azurerm_subnet_route_table_association" "this" {
for_each = { for k, v in local.subnets : k => v if lookup(v, "route_table", "") != "" }
subnet_id = azurerm_subnet[each.value].id
route_table_id = azurerm_route_table[each.value].route_table.id
}
ISSUE:
I am getting "Error: Invalidate Reference... A reference to a resource type must be followed by at least one attribute access, specifying the resource name." during TF validate on the below lines in main.tf:
subnet_id
rout_table_id
I don't think I setup the resource reference loop correctly and need some guidance. Thanks in advance.
I'm assuming you are creating a collection of azurerm_subnet resources named "mysubnet" (you are not show that part on your example).
So, the item of your collection is the resource itself, not the type of resource. You should do like this:
resource "azurerm_subnet_route_table_association" "this" {
for_each = { for k, v in local.subnets : k => v if lookup(v, "route_table", "") != "" }
subnet_id = azurerm_subnet.mysubnet[each.key].id
route_table_id = azurerm_route_table.routetable[each.key].id
}
Pay special attention in the reference of mysubnet[each.key] element. The each.key here should be the same key you've used in the azurerm_subnet definition.
I'm unable to test it right now, but I'm confident that it's the way to go.

terraform - azure vnets by variables

terraform version: v0.12.24
provider.azurerm v2.49.0
I am trying to build a vnet by having all the line items as variables and then populate them in
the variables.auto.tfvars file.
for my main.tf section:
resource "azurerm_virtual_network" "vnet" {
name = var.vnet_name
resource_group_name = var.rg_grp_name
address_space = var.vnet_cidr
location = var.region_location #match RG location
dns_servers = var.vnet_dns_servers
#tags stuff here
}
Resource "azurerm_subnet" "subnet" {
for_each = var.subnets
name = lookup(each.value, "name")
resource_group_name = var.rg_grp_name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefix = lookup(each.value, "cidr")
}
and in my variables.tf, which i am really unsure about;
###vnet section###################
#variable for defining what region
variable "name" {
type = string
}
#variable for defining what region
variable "resource_group_name" {
type = string
}
#variable for defining what region
variable "vnet_cidr" {
description = "address space"
type = list
}
#variable for defining what region
variable "region_location" {
type = string
}
#variable for defining what region
variable "vnet_dns_servers" {
type = list
}
#variable for defining what region
variable "subnets" {
type = map(object({
name = string
cidr = string
}))
}
##end subnet block
#variable for defining what region
variable "azregion" {
type = string
}
#####end vnet section#############
I dont have the variables.auto.tfvars section yet...
whats the best way to go about this?
Thanks
We can create the input variables by variable blocks but reference them as attributes on an object named var. We can access its value from within expressions as var.<NAME>, where <NAME> matches the label given in the declaration block.
So, according to the expressions in your main.tf file, the variable declaration block shoule be like this:
variable "vnet_name" {
description = "Name of the vnet to create"
type = string
# default = "acctvnet"
}
variable "rg_grp_name" {
description = "Name of the resource group to be imported."
type = string
# default = "testrg"
}
variable "vnet_cidr" {
type = list(string)
description = "The address space that is used by the virtual network."
# default = ["10.0.0.0/16"]
}
variable "region_location" {
type = string
# default = "eastus"
}
# If no values specified, this defaults to Azure DNS
variable "vnet_dns_servers" {
description = "The DNS servers to be used with vNet."
type = list(string)
# default = []
}
variable "subnets" {
type = map(object({
name = string
cidr = string
}))
# default = {
# subnet1 = {
# name = "subnet1"
# cidr = "10.0.1.0/24"
# },
# subnet2 = {
# name = "subnet2"
# cidr = "10.0.2.0/24"
# }
# }
}
If you would like to make terraform automatically load a number of variable definitions files, you can input the default values for that variable as the expression variable_name = default_value in the variables.auto.tfvarsfile .
vnet_name = "acctvnet"
rg_grp_name = "testrg"
vnet_cidr = ["10.0.0.0/16"]
region_location = "eastus"
vnet_dns_servers = []
subnets = {
subnet1 = {
name = "subnet1"
cidr = "10.0.1.0/24"
},
subnet2 = {
name = "subnet2"
cidr = "10.0.2.0/24"
}
}
You can check the source code in the vnet module with terraform for more details.

Use output value from module that has a for_each set

I had my code setup to export the dynamic private ip address when the VM is created. I did this via an outputs value. Since then, I have updated to tf 0.13 and I'm using a for_each in the module but when I reference this value now I get the below error. I'm not sure how I can export the dynamic private address attribute of the NIC now the for_each has been set to be used in the source_address_prefixes. I understand what the error is saying but not sure on correct way of exporting the value to the object map?
Error: Unsupported attribute
on main.tf line 66, in resource "azurerm_network_security_rule" "fico-app-sr-80":
66: source_address_prefixes = module.fico_web_vm.linux_vm_ips[0]
|----------------
| module.fico_web_vm is object with 1 attribute "web-1"
This object does not have an attribute named "linux_vm_ips".
The outputs.tf file:
output "linux_vm_names" {
value = [azurerm_linux_virtual_machine.virtual_machine.*.name]
}
output "linux_vm_ips" {
value = [azurerm_network_interface.network_interface.*.private_ip_address]
}
output "linux_vm_nsg" {
value = azurerm_network_security_group.network_security_group.name
}
Security rule where the error is referencing, located in the main.tf:
resource "azurerm_network_security_rule" "fico-app-sr-80" {
name = "nsr-${var.environment}-${var.directorate}-${var.business_unit}-${var.vm_identifier}${var.instance_number}-http80"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
destination_port_range = "80"
source_address_prefixes = module.fico_web_vm.linux_vm_ips[0]
destination_address_prefix = "VirtualNetwork"
resource_group_name = azurerm_resource_group.rg_dwp_fico_app.name
network_security_group_name = "module.fico_app_vm.linux_vm_nsg"
}
The NIC which is in the root module:
resource "azurerm_network_interface" "network_interface" {
name = "nic-${var.environment}-${var.directorate}-${var.business_unit}-${var.vm_identifier}-${var.vm_name}"
resource_group_name = var.resource_group
location = var.location
enable_ip_forwarding = "false"
enable_accelerated_networking = "false"
ip_configuration {
name = "ipconfig1"
subnet_id = data.azurerm_subnet.dwp_subnet.id
private_ip_address_allocation = "Dynamic"
primary = "true"
}
The module that builds the web_vm in main.tf:
module "fico_web_vm" {
for_each = var.web_servers
source = "../modules/compute/linux_vm"
source_image_id = var.web_image_id
location = var.location
vm_name = each.key
vm_identifier = "${var.vm_identifier}${var.instance_number}"
vm = each.value
disks = each.value["disks"]
resource_group = azurerm_resource_group.rg_dwp_fico_web.name
directorate = var.directorate
business_unit = var.business_unit
environment = var.environment
network_rg_identifier = var.network_rg_identifier
subnet_name = "sub-dwp-${var.environment}-${var.directorate}-${var.business_unit}-fe01"
diag_storage_account_name = var.diag_storage_account_name
ansible_storage_account_name = var.ansible_storage_account_name
ansible_storage_account_key = var.ansible_storage_account_key
log_analytics_workspace_name = var.log_analytics_workspace_name
backup_policy_name = var.backup_policy_name
}
variables for the app in the main.tf:
# Web VM Variables
variable "web_servers" {
description = "Variable for defining each instance"
type = map(object({
size = string
admin_username = string
public_key = string
disks = list(number)
zone_vm = string
zone_disk = list(string)
}))
}
The tfvars file that the variables.tf translates to and which the for_each loops over:
web_servers ={
web-1 = {
size = "Standard_B2s"
admin_username = xxxx
public_key = xxxx
disks = [32, 32]
zone_vm = "1"
zone_disk = ["1"]
}
}
As I see, there two mistakes in your code.
First:
network_security_group_name = "module.fico_app_vm.linux_vm_nsg"
need to change into:
network_security_group_name = module.fico_app_vm.linux_vm_nsg
The double quotes are usually used to set a string when you use them only, not other resources.
Second:
source_address_prefixes = module.fico_web_vm.linux_vm_ips[0]
need to change into:
source_address_prefixes = module.fico_web_vm.linux_vm_ips
and the output also should be change into:
output "linux_vm_ips" {
value = azurerm_network_interface.network_interface.*.private_ip_address
}
I see you just create one NIC without count and for_each, but when you set the value with azurerm_network_interface.network_interface.*.private_ip_address then it also returns a list. And the source_address_prefixes option expects a list, so you just need to give the output.
Maybe it's better to change the output like this:
output "linux_vm_ip" {
value = azurerm_network_interface.network_interface.private_ip_address
}
And then you can set the source_address_prefixes like this:
source_address_prefixes = [ module.fico_web_vm.linux_vm_ip ]

Resources