List & String Conversion Issue for Data sources - azure

I am caught in a bit of a loop on this one. Need to provide the azure_windows_virtual_machine with a list of network interface IDs. The network interfaces are created using a separate resource block. In my variable definition for the windows vm, I provide an argument for the name[s] of said network interfaces so that we can correctly associate the nics that we want with each virtual machine. If we have 100 nics and 90 VMs, some of the VMs could get two NICs, so we want to be sure we provide some link between NIC name and VM name.
The network interface names are therefore a list(string).
I have been trying to use the values function to get the list of NIC IDs (given the names), but running into a failure: "The each object can be used only in "module" or "resource" blocks, and only when the "for_each" argument is set."
If I use a data source in the resource block, which seemed most logical, it fails too because I have a list(string) specified for the network_interface_names argument, but the data source cannot take that. It of course needs a single string. But it's never going to be a single string, it's always going to be a list (since we can have more than one NIC per VM).
I think the correct answer is to maybe create the list of IDs beforehand - trick is that it would need to almost be dynamic for each defined VM - because each VM will have a different list of network_interface_names. We therefore need to generate the new list on the fly for each VM.
Variables
variable "resource_groups" {
description = "Resource groups"
type = map(object({
location = string
}))
}
variable "virtual_networks" {
description = "virtual networks and properties"
type = map(object({
resource_group_name = string
address_space = list(string)
}))
}
variable "subnets" {
description = "subnet and their properties"
type = map(object({
resource_group_name = string
virtual_network_name = string
address_prefixes = list(string)
}))
}
variable "nic" {
description = "network interfaces"
type = map(object({
subnet_name = string
resource_group_name = string
}))
}
variable "admin_password" {
type = string
sensitive = true
}
variable "admin_user" {
type = string
sensitive = true
}
variable "windows_vm" {
description = "Windows virtual machine"
type = map(object({
network_interface_names = list(string)
resource_group_name = string
size = string
timezone = string
}))
}
INPUTS
resource_groups = {
rg-eastus-dev1 = {
location = "eastus"
}
}
virtual_networks = {
vnet-dev1 = {
resource_group_name = "rg-eastus-dev1"
address_space = ["10.0.0.0/16"]
}
}
subnets = {
snet-01 = {
resource_group_name = "rg-eastus-dev1"
virtual_network_name = "vnet-dev1"
address_prefixes = ["10.0.1.0/24"]
}
}
nic = {
nic1 = {
subnet_name = "snet-01"
resource_group_name = "rg-eastus-dev1"
}
}
admin_password = "s}8cpH96qa.1BQ"
admin_user = "padmin"
windows_vm = {
winvm1 = {
network_interface_names = ["nic1"]
resource_group_name = "rg-eastus-dev1"
size = "Standard_B2s"
timezone = "Eastern Standard Time"
}
}
MAIN
resource "azurerm_resource_group" "rgs" {
for_each = var.resource_groups
name = each.key
location = each.value["location"]
}
data "azurerm_resource_group" "rgs" {
for_each = var.resource_groups
name = each.key
depends_on = [
azurerm_resource_group.rgs
]
}
resource "azurerm_virtual_network" "vnet" {
for_each = var.virtual_networks
name = each.key
resource_group_name = each.value["resource_group_name"]
address_space = each.value["address_space"]
location = data.azurerm_resource_group.rgs[each.value["resource_group_name"]].location
}
resource "azurerm_subnet" "subnet" {
for_each = var.subnets
name = each.key
resource_group_name = each.value["resource_group_name"]
virtual_network_name = each.value["virtual_network_name"]
address_prefixes = each.value["address_prefixes"]
depends_on = [
azurerm_virtual_network.vnet
]
}
data "azurerm_subnet" "subnet" {
for_each = var.subnets
name = each.key
virtual_network_name = each.value["virtual_network_name"]
resource_group_name = each.value["resource_group_name"]
depends_on = [
azurerm_resource_group.rgs
]
}
resource "azurerm_network_interface" "nics" {
for_each = var.nic
ip_configuration {
name = each.key
subnet_id = data.azurerm_subnet.subnet[each.value["subnet_name"]].id
private_ip_address_allocation = "Dynamic"
}
location = data.azurerm_resource_group.rgs[each.value["resource_group_name"]].location
name = each.key
resource_group_name = each.value["resource_group_name"]
depends_on = [
azurerm_resource_group.rgs,
azurerm_subnet.subnet
]
}
data "azurerm_network_interface" "nics" {
for_each = var.nic
name = each.key
resource_group_name = each.value["resource_group_name"]
depends_on = [
azurerm_resource_group.rgs
]
}
resource "azurerm_windows_virtual_machine" "windows_vm" {
for_each = var.windows_vm
admin_password = var.admin_password
admin_username = var.admin_user
location = data.azurerm_resource_group.rgs[each.value["resource_group_name"]].location
name = each.key
network_interface_ids = values(data.azurerm_network_interface.nics[each.value["network_interface_names"]].id)
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
resource_group_name = each.value["resource_group_name"]
size = each.value["size"]
timezone = each.value["timezone"]
source_image_reference {
publisher = "MicrosoftWindowsServer"
offer = "WindowsServer"
sku = "2019-Datacenter"
version = "latest"
}
}
Current Error on Plan
╷
│ Error: Invalid index
│
│ on main.tf line 75, in resource "azurerm_windows_virtual_machine" "windows_vm":
│ 75: network_interface_ids = values(data.azurerm_network_interface.nics[each.value["network_interface_names"]].id)
│ ├────────────────
│ │ data.azurerm_network_interface.nics is object with 1 attribute "nic1"
│ │ each.value["network_interface_names"] is list of string with 1 element
│
│ The given key does not identify an element in this collection value: string required.
Possible Solution - But Not working
Provide a map, keyed off the VM name, of NIC IDs. Then, in the windows_vm resource, take that map and try to get the list of NIC ID values.
locals {
nic_ids {
[for k, v in var.windows_vm : k => v {data.azurerm_network_interface.nics[v.network_interface_names]}.id]
}
}
resource "azurerm_windows_virtual_machine" "windows_vm" {
for_each = var.windows_vm
admin_password = var.admin_password
admin_username = var.admin_user
location = data.azurerm_resource_group.rgs[each.value["resource_group_name"]].location
name = each.key
network_interface_ids = values(local.nic_ids[each.key])
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
resource_group_name = each.value["resource_group_name"]
size = each.value["size"]
timezone = each.value["timezone"]
source_image_reference {
publisher = "MicrosoftWindowsServer"
offer = "WindowsServer"
sku = "2019-Datacenter"
version = "latest"
}
}

First of all, you do not need to call data after creation of each resource. The resource itself will contain all the information that you need. So you should eliminate all data sources in your code and use resource directly.
But returning to the error you provided. One way to generate the list dynamically, would be:
network_interface_ids = [for ni_name in each.value["network_interface_names"]: azurerm_network_interface.nics[ni_name].id]

Related

Terraform Azure for each VM / NIC

I'm trying to create multiplane vms using for each function in terraform.
Resource Group
resource "azurerm_resource_group" "rg" {
name = "${var.prefix}-rg"
location = "east us 2"
tags = var.tags
}
VNET
resource "azurerm_virtual_network" "vnet" {
name = "${var.prefix}-network-1"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
tags = var.tags
}
Subnet
resource "azurerm_subnet" "subnet" {
name = "${var.prefix}-network-subnet-1"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.2.0/24"]
}
Variables for NICS
variable "nics" {
type = map
default = {
nic3 = {
name = "ubuntu-test-3"
}
nic4 = {
name = "ubuntu-test-4"
}
}
}
NICS
resource "azurerm_network_interface" "nics" {
for_each = var.nics
name = each.value.name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
ip_configuration {
name = "${each.value.name}-conf-1"
subnet_id = azurerm_subnet.subnet.id
private_ip_address_allocation = "Dynamic"
}
tags = var.tags
}
Variables for VMS
variable "vms" {
description = "Virtual Machines"
type = map
default = {
vm3 = {
name = "ubuntu-test-3"
size = "Standard_DS1_v2"
}
vm4 = {
name = "ubuntu-test-4"
size = "Standard_DS1_v2"
}
}
}
and the block for the VM ( not completed - i wrote only the section that i have issue with )
resource "azurerm_virtual_machine" "vms" {
for_each = var.vms
name = each.value.name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
vm_size = each.value.size
tags = var.tags
network_interface_ids = [
azurerm_network_interface.nics[each.value].id,
]
The issue is with this section
network_interface_ids = [
azurerm_network_interface.nics[each.value].id,
]
I'm getting ERROR
│ Error: Invalid index
│
│ on main.tf line 247, in resource "azurerm_virtual_machine" "vms":
│ 247: azurerm_network_interface.nics[each.value].id,
│ ├────────────────
│ │ azurerm_network_interface.nics is object with 2 attributes
│ │ each.value is object with 2 attributes
│
│ The given key does not identify an element in this collection value: string required.
Also tried with
network_interface_ids = [
azurerm_network_interface.nics[each.key].id,
]
and got ERROR
│ Error: Invalid index
│
│ on main.tf line 249, in resource "azurerm_virtual_machine" "vms":
│ 249: azurerm_network_interface.nics[each.key].id,
│ ├────────────────
│ │ azurerm_network_interface.nics is object with 2 attributes
│ │ each.key is "vm3"
│
│ The given key does not identify an element in this collection value.
╵
╷
│ Error: Invalid index
│
│ on main.tf line 249, in resource "azurerm_virtual_machine" "vms":
│ 249: azurerm_network_interface.nics[each.key].id,
│ ├────────────────
│ │ azurerm_network_interface.nics is object with 2 attributes
│ │ each.key is "vm4"
│
│ The given key does not identify an element in this collection value
What I'm doing wrong ?
Replicated the same scenario and able to create resources.
Made couple of changes for the existing code base provided
Added **nic = "nic" value at vms block
Updated network_interface_ids = [azurerm_network_interface.nics[each.value.nic].id,]
Here is the code snippet.
Step1:
Main tf code as below
provider "azurerm" {
features {}
}
variable "prefix" {
default = "rg_swarna"
}
resource "azurerm_resource_group" "rg" {
name = "${var.prefix}-rg"
location = "West US"
// tags = var.tags
}
resource "azurerm_virtual_network" "vnet" {
name = "${var.prefix}-network-1"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
// tags = var.tags
}
resource "azurerm_subnet" "subnet" {
name = "${var.prefix}-network-subnet-1"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.2.0/24"]
}
resource "azurerm_network_interface" "nics" {
for_each = var.nics
name = each.value.name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
ip_configuration {
name = "${each.value.name}-conf-1"
subnet_id = azurerm_subnet.subnet.id
private_ip_address_allocation = "Dynamic"
}
//tags = var.tags
}
resource "azurerm_virtual_machine" "vms" {
for_each = var.vms
name = each.value.name
vm_size = "Standard_DS1_v2"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
network_interface_ids = [azurerm_network_interface.nics[each.value.nic].id,]
storage_os_disk {
name = "myosdisk${each.value.name}"
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Standard_LRS"
}
storage_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
os_profile {
computer_name = "TestDemo"
admin_username = "azureuser"
admin_password = "*****#123"
}
os_profile_linux_config {
disable_password_authentication = false
}
}
Step2:
variable tf file
variable "allowed_subnet_ids" {
type = list(string)
description = "access"
}
variable "nics" {
type = map
default = {
nic3 = {
name = "ubun3"
}
nic4 = {
name = "ubun4"
}
}
}
variable "vms" {
description = "VM"
type = map
default = {
vm3 = {
name = "ubun3"
size = "Standard_DS1_v2"
nic = "nic3"
}
vm4 = {
name = "ubuntu4"
size = "Standard_DS1_v2"
nic = "nic4"
}
}
}
variable "allowed_ips" {
type = list(string)
description = "IP addresses"
}
variable "sku" {
type = string
description = "SKU"
}
variable "resource_group_name" {
type = string
description = "resource_group_name"
}
variable "location" {
type = string
description = "location"
}
Step3:
terraform plan
terraform apply -auto-approve
Here are the reference screenshots
Here is the output from above code
In order for this to work, you would need to modify the variable for VMs slightly:
variable "vms" {
description = "Virtual Machines"
type = map
default = {
vm3 = {
name = "ubuntu-test-3"
size = "Standard_DS1_v2"
nic = "nic3"
}
vm4 = {
name = "ubuntu-test-4"
size = "Standard_DS1_v2"
nic = "nic4"
}
}
}
Then, in the VM resource block:
resource "azurerm_virtual_machine" "vms" {
for_each = var.vms
name = each.value.name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
vm_size = each.value.size
tags = var.tags
network_interface_ids = [
azurerm_network_interface.nics[each.value.nic].id,
]
}
Alternatively, you could try with resource chaining with for_each [1], but then you would have to refactor the resource block a bit:
resource "azurerm_virtual_machine" "vms" {
for_each = azurerm_network_interface.nics
name = each.value.name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
vm_size = var.vm_size # or set it to be equal to "Standard_DS1_v2"
tags = var.tags
network_interface_ids = [
each.value.id,
]
}
Then, you would also have to define a new variable called vm_size:
variable "vm_size" {
type = string
description = "VM size."
default = "Standard_DS1_v2"
}
In the second case, you could remove the variable vms completely.
[1] https://developer.hashicorp.com/terraform/language/meta-arguments/for_each#chaining-for_each-between-resources

terraform multiple vm with separate module for nic and separate module for vm .. both NIC get attached to first vm that gets created

I have created separate modules for vnet, NIC and VM.. I am trying to create two vms in the vm module and two nics in the nic module... created an output in the nic module to get the nic.id and this output am referring in the vm module , but only one vm gets created with two nics and second vm fails to create due to unavailability of nic... please find my code below, i need to be able to map the individual nic in the nic module to individual vm in the vm moodule
main.tf
module "nic" {
source = "./Nic"
resource_group_name = module.vnet1mod.rgnameout
location = module.vnet1mod.rglocationout
subnet_id = module.vnet1mod.subnetout
}
module "vnet1mod" {
source = "./vnetmodule"
}
module "virtualmachine" {
source = "./VirtualMachine"
resource_group_name = module.vnet1mod.rgnameout
location = module.vnet1mod.rglocationout
network_interface_ids = module.nic.netinterfaceoutput # this is where its failing !!
}
..............
nic module
resource "azurerm_network_interface" "nic1" {
for_each = var.vmdetails
name = each.value.vmnic
location = var.location
resource_group_name = var.resource_group_name
ip_configuration {
name = "internal"
subnet_id = var.subnet_id
private_ip_address_allocation = "Dynamic"
}
}
output "netinterfaceoutput" {
value = tomap({ for k, s in azurerm_network_interface.nic1 : k => s.id })
}
variable "location" {`enter code here`
type = string
description = "(optional) describe your variable"
}
variable "resource_group_name" {
type = string
description = "(optional) describe your variable"
}
variable "subnet_id" {
type = string
description = "(optional) describe your variable"
}
...........
vm module
resource "azurerm_windows_virtual_machine" "vm1" {
for_each = var.vmdetails
name = each.value.vmname
resource_group_name = var.resource_group_name
location = var.location
size = var.vmsize
admin_username = var.adminusername
admin_password = var.adminpassword
network_interface_ids = var.network_interface_ids
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = var.publisher
offer = var.offer
sku = var.sku
version = var.Osversion
}
}
variable "vmdetails" {
type = map(any)
default = {
"vm1" = {
vmname = "vmA-1"
vmnic = "vmnicA-1"
}
"vm2" = {
vmname = "vmA-2"
vmnic = "vmnicA-2"
}
}
}
........
vnet module
resource "azurerm_virtual_network" "vnet1" {
name = var.vnet_name
location = var.location_name
resource_group_name = var.resourcegroup1_name
address_space = var.vnet_address
}
resource "azurerm_subnet" "subnet1" {
name = var.subnet_name
resource_group_name = var.resourcegroup1_name
virtual_network_name = azurerm_virtual_network.vnet1.name
address_prefixes = var.subnet_address
}
output "rgnameout" {
value = azurerm_virtual_network.vnet1.resource_group_name
}
output "rglocationout" {
value = azurerm_virtual_network.vnet1.location
}
output "subnetout" {
value = azurerm_subnet.subnet1.id
}

Terraform - Consume for_each module output as input to another module

I'm having trouble referencing the output of a module as an input to another module.
I'm trying to output network_interface_id from vm.tf, and use it as input to lb.tf.
I get the error, each.value is tuple with 2 elements, Inappropriate value for attribute "network_interface_id": string required.
It works if I use network_interface_id = each.value[0], or[1] but obviously only adds one nic to the lb.
I've been going round in circles trying to figure this out so any help would be much appreciated.
Many thanks in advance ... :-)
This is the snippet of code I'm struggling with from lb.tf. Full code is below that.
resource "azurerm_network_interface_backend_address_pool_association" "nibapa" {
for_each = var.nic_ids
network_interface_id = each.value # <= This errors with each.value is tuple with 2 elements. It works using each.value[0] or [1]; but I need to loop through both.
ip_configuration_name = "internal"
backend_address_pool_id = azurerm_lb_backend_address_pool.lbap.id
}
main.tf
locals {
vm = {
"01" = {
zone = "1"
}
"02" = {
zone = "2"
}
}
}
resource "azurerm_resource_group" "rg" {
location = "northeurope"
name = "rg-test1"
}
module "vm" {
source = "./vm"
for_each = local.vm
location = "northeurope"
resource_group_name = azurerm_resource_group.rg.name
vm_name = "vm-${each.key}"
nic_name = "nic-vm-${each.key}"
os_disk_name = "osdisk-vm-${each.key}"
availability_zone = each.value.zone
}
output "nic_ids" { value = [ for k, nic in module.vm : nic.nic_id ] }
module "lb" {
source = "./lb"
location = "northeurope"
resource_group_name = azurerm_resource_group.rg.name
nic_ids = { value = [ for k, nic in module.vm : nic.nic_id ] }
}
vm.tf
variable "location" {}
variable "resource_group_name" {}
variable "vm_name" {}
variable "nic_name" {}
variable "os_disk_name" {}
variable "availability_zone" {}
resource "azurerm_network_interface" "ni" {
location = var.location
resource_group_name = var.resource_group_name
name = var.nic_name
ip_configuration {
name = "internal"
subnet_id = "/subscriptions/2bc7b65e-18d6-42ae-afb2-e66d50be6b05/resourceGroups/rg-core-01/providers/Microsoft.Network/virtualNetworks/vnet-prd-spoke-nteu-01/subnets/snet-app"
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_linux_virtual_machine" "lvm" {
location = var.location
resource_group_name = var.resource_group_name
name = var.vm_name
size = "Standard_B2ms"
zone = var.availability_zone
admin_username = "ladmin"
admin_password = "Password1234"
disable_password_authentication = false
network_interface_ids = [
azurerm_network_interface.ni.id,
]
os_disk {
name = var.os_disk_name
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
}
output "nic_id" { value = azurerm_network_interface.ni.id }
lb.tf
variable "location" {}
variable "resource_group_name" {}
variable "nic_ids" {}
resource "azurerm_lb" "lb" {
location = var.location
resource_group_name = var.resource_group_name
name = "lbi-test1"
sku = "Standard"
sku_tier = "Regional"
frontend_ip_configuration {
name = "feip-test1"
subnet_id = "/subscriptions/2bc7b65e-18d6-42ae-afb2-e66d50be6b05/resourceGroups/rg-core-01/providers/Microsoft.Network/virtualNetworks/vnet-prd-spoke-nteu-01/subnets/snet-app"
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_lb_backend_address_pool" "lbap" {
loadbalancer_id = azurerm_lb.lb.id
name = "beap-test1"
}
resource "azurerm_network_interface_backend_address_pool_association" "nibapa" {
for_each = var.nic_ids
network_interface_id = each.value # <= This errors with each.value is tuple with 2 elements. It works using each.value[0] or [1]; but I need to loop through both.
ip_configuration_name = "internal"
backend_address_pool_id = azurerm_lb_backend_address_pool.lbap.id
}
The error code is:
Error: Incorrect attribute value type
on lb\lb.tf line 25, in resource "azurerm_network_interface_backend_address_pool_association" "nibapa":
25: network_interface_id = each.value
each.value is tuple with 2 elements
Inappropriate value for attribute "network_interface_id": string required.
I figured this out in the end. The key changes were mainly syntactical ...
main.tf
The key line here is 'nic_ids = module.vm'. This returns a map of nic_id's for each instance.
module "lb" {
depends_on = [ module.vm ]
source = "./lb"
location = "northeurope"
resource_group_name = azurerm_resource_group.rg.name
nic_ids = module.vm
}
output "nic_ids" { value = module.vm }; (so the output of the variable nic_ids to pass to the next module looks like this)
nic_ids = {
"01" = {
"nic_id" = "/subscriptions/2bc7b65e-18d6-42ae-afb2-e66d50be6b05/resourceGroups/rg-prd-oem-2208081500/providers/Microsoft.Network/networkInterfaces/nic-prdnteuoms01"
}
"02" = {
"nic_id" = "/subscriptions/2bc7b65e-18d6-42ae-afb2-e66d50be6b05/resourceGroups/rg-prd-oem-2208081500/providers/Microsoft.Network/networkInterfaces/nic-prdnteuoms02"
}
}
lb.tf
The key line here is 'network_interface_id = each.value.nic_id'.
resource "azurerm_network_interface_backend_address_pool_association" "nibapa" {
for_each = var.nic_ids
network_interface_id = each.value.nic_id
ip_configuration_name = "internal"
backend_address_pool_id = azurerm_lb_backend_address_pool.lbap.id
}
Simples when you know how ;-)

A managed resource "azurerm_network_interface" has not been declared in module

I'm running a "terraform plan" against my Linux VM and I'm receiving the following error:
│ Error: Reference to undeclared resource
│
│ on .terraform/modules/vm-ansiblecontroller/virtual-machine/linux/outputs.tf line 13, in output "nic_id":
│ 13: value = azurerm_network_interface.nic-linux.id
│
│ A managed resource "azurerm_network_interface" "nic-linux" has not been declared in module.vm-ansiblecontroller.
I haven't included any code from my RGs & vNets, as I'm hoping what I have included is enough to solve this.
Any assistance would be appreciated, I just can't figure it out
module "vm-ansiblecontroller" {
resource_group_name = module.rg-ansiblecontroller.resource_group_name
location = local.location
linux_machine_name = "linux-test1"
tags = var.tags
nic_id = module.vm-ansiblecontroller.nic_id
subnet_id = module.subnet-networkcore.subnet_id
virtual_machine_size = "Standard_D2"
admin_username = "jpadmin"
admin_ssh_public_key = file("~/.ssh/id_rsa.pub")
source_image_publisher = "Canonical"
source_image_offer = "UbuntuServer"
source_image_sku = "16.04-LTS"
source_image_version = "latest"
operating_system_disk_cache = "ReadWrite"
operating_system_disk_type = "Standard_LRS"
ip_configuration_name = "internal"
private_ip_address_allocation = "Dynamic"
public_ip_allocation_method = "Static"
public_ip_sku = "Standard"
}
modules/virtualmachine/main.tf
# Linux Virtual Machine
resource "azurerm_linux_virtual_machine" "vm-linux" {
name = var.linux_machine_name
location = var.location
resource_group_name = var.resource_group_name
tags = var.tags
size = var.virtual_machine_size
admin_username = var.admin_username
disable_password_authentication = true
# network_interface_ids = [azurerm_network_interface.nic-linux.id]
network_interface_ids = var.nic_id
admin_ssh_key {
username = var.admin_username
public_key = var.admin_ssh_public_key
}
source_image_reference {
publisher = var.source_image_publisher
offer = var.source_image_offer
sku = var.source_image_sku
version = var.source_image_version
}
os_disk {
caching = var.operating_system_disk_cache
storage_account_type = var.operating_system_disk_type
}
}
# Network Interfaces for Linux VM
resource "azurerm_network_interface" "nic-linux" {
name = var.linux_machine_name
location = var.location
resource_group_name = var.resource_group_name
tags = var.tags
ip_configuration {
name = var.ip_configuration_name
# subnet_id = azurerm_subnet.subnet.id
subnet_id = var.subnet_id
private_ip_address_allocation = var.private_ip_address_allocation
public_ip_address_id = azurerm_public_ip.pip-linux.id
}
}
resource "azurerm_public_ip" "pip-linux" {
name = var.linux_machine_name
location = var.location
resource_group_name = var.resource_group_name
tags = var.tags
allocation_method = var.public_ip_allocation_method
sku = var.public_ip_sku
}
modules/virtualmachine/variables.tf
# VM Name
variable "linux_machine_name" {
description = "Linux Virtual Machine Name - If left blank generated from metadata module"
type = string
default = ""
}
variable "resource_group_name" {
description = "Resource group name"
type = string
}
variable "location" {
description = "Azure region"
type = string
}
variable "tags" {
description = "tags to be applied to resources"
type = map(string)
}
# VM Size
variable "virtual_machine_size" {
description = "Instance size to be provisioned"
type = string
}
variable "admin_username" {
description = "names to be applied to resources"
type = string
}
variable "admin_ssh_public_key" {
description = "(Linux) Public SSH Key - Generated if left blank"
type = string
default = ""
sensitive = true
}
# Operating System
variable "source_image_publisher" {
description = "Operating System Publisher"
type = string
}
variable "source_image_offer" {
description = "Operating System Name"
type = string
}
variable "source_image_sku" {
description = "Operating System SKU"
type = string
}
variable "source_image_version" {
description = "Operating System Version"
type = string
default = "latest"
}
# Operating System Disk
variable "operating_system_disk_cache" {
description = "Type of caching to use on the OS disk - Options: None, ReadOnly or ReadWrite"
type = string
default = "ReadWrite"
}
variable "operating_system_disk_type" {
description = "Type of storage account to use with the OS disk - Options: Standard_LRS, StandardSSD_LRS or Premium_LRS"
type = string
default = "StandardSSD_LRS"
}
variable "ip_configuration_name" {
description = "ip configuration name"
type = string
default = ""
}
# Networking
variable "nic_id" {
type = list(string)
description = "ID of the nic"
}
variable "subnet_id" {
type = string
description = "ID of the subnet"
}
variable "private_ip_address_allocation" {
type = string
description = "Private ip allocation method"
}
variable "public_ip_allocation_method" {
type = string
description = "Public ip allocation method"
}
variable "public_ip_sku" {
description = "SKU to be used with this public IP - Basic or Standard"
type = string
default = "Standard"
}
modules/virtualmachine/outputs.tf
output "nic_id" {
description = "ids of the vm nics provisoned."
value = azurerm_network_interface.nic-linux.id
}
NEW ERROR:
Error: Invalid value for module argument
│
│ on compute_lin_vm.tf line 10, in module "vm-ansiblecontroller":
│ 10: nic_id = module.vm-ansiblecontroller.nic_id
│
│ The given value is not suitable for child module variable "nic_id" defined at
│ .terraform/modules/vm-ansiblecontroller/virtual-machine/linux/variables.tf:83,1-18: list of string required.
You placed your azurerm_network_interface inside azurerm_linux_virtual_machine. It should be:
resource "azurerm_linux_virtual_machine" "vm-linux" {
name = var.linux_machine_name
location = var.location
resource_group_name = var.resource_group_name
tags = var.tags
size = var.virtual_machine_size
admin_username = var.admin_username
disable_password_authentication = true
# network_interface_ids = [azurerm_network_interface.nic-linux.id]
network_interface_ids = var.nic_id
admin_ssh_key {
username = var.admin_username
public_key = var.admin_ssh_public_key
}
source_image_id = var.source_image_id
custom_data = var.custom_data
source_image_reference {
publisher = var.source_image_publisher
offer = var.source_image_offer
sku = var.source_image_sku
version = var.source_image_version
}
os_disk {
caching = var.operating_system_disk_cache
storage_account_type = var.operating_system_disk_type
}
}
# Network Interfaces for Linux VM
resource "azurerm_network_interface" "nic-linux" {
name = var.linux_machine_name
location = var.location
resource_group_name = var.resource_group_name
tags = var.tags
ip_configuration {
name = var.ip_configuration_name
subnet_id = azurerm_subnet.subnet.id
private_ip_address_allocation = var.private_ip_address_allocation
public_ip_address_id = azurerm_public_ip.pip-linux.id
}
}

pass multiple outputs to a different module in Terraform

I'm trying to cover each parts as modules rather than keeping it in a single main.tf file.
My intention is to create 1 vnet (TESTVNET), multiple subnets, NIC's for each subnet.
I'm able to reach till creating multiple subnets in my Vnet. What I'm looking for is on how to pass the subnet ID's to NETWORKINTERFACE module. Please find my code below;
Main.tf
resource "azurerm_resource_group" "resource_group" {
name = var.RGname
location = var.RGlocation
}
module "VNET" {
source = "./Modules/NetworkConfig"
name = var.VNETname
address_space = var.address_space
location = var.RGlocation
resource_group_name = azurerm_resource_group.resource_group.name
}
module "SUBNETS" {
source = "./Modules/SubnetConfig"
Subnetlist = var.Subnetlist
virtual_network_name = module.VNET.vnet_name
resource_group_name = azurerm_resource_group.resource_group.name
depends_on = [azurerm_resource_group.resource_group, module.VNET.vnet]
}
module "NETWORKINTERFACE" {
source = "./Modules/NIConfig"
niclist = var.niclist
resource_group_name = azurerm_resource_group.resource_group.name
location = azurerm_resource_group.resource_group.location
SUBNET.tf
resource "azurerm_subnet" "SUBNETS" {
for_each=var.Subnetlist
name=each.value.name
address_prefixes=[each.value.address]
resource_group_name = var.resource_group_name
virtual_network_name = var.virtual_network_name
}
output "subnet_ids" {
value = values(azurerm_subnet.SUBNETS)[*].id
}
NETWORKINTERFACE.tf
resource "azurerm_network_interface" "NETWORKINTERFACE" {
for_each=var.niclist
name = each.value.name
location = var.location
resource_group_name = var.resource_group_name
ip_configuration {
name = "ipconfig1"
subnet_id = # part where I'm confused
private_ip_address_allocation = "Dynamic"
}
}
How can I pass the output values from Subnet module (3 subnet in total=3 id's) to NETWORKINTERFACE module, where it should take each of these id's in for_each=var.niclist loop.
If you find this approach incorrect, let me know
PS. I'm new to terraform
-- update 2
please find my 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" }
}
this is how the variable is populated. I have used the suggestion from #martin and modified few commands. It is now creating subnets and NIC's the way I wanted.
Main.tf
module "VNET" {
source = "./Modules/NetworkConfig"
name = var.VNETname
address_space = var.address_space
location = var.RGlocation
resource_group_name = azurerm_resource_group.resource_group.name
}
module "SUBNETS" {
source = "./Modules/SubnetConfig"
Subnetlist = var.Subnetlist
virtual_network_name = module.VNET.vnet_name
resource_group_name = azurerm_resource_group.resource_group.name
depends_on = [azurerm_resource_group.resource_group, module.VNET.vnet]
}
module "NETWORKINTERFACE" {
source = "./Modules/NIConfig"
resource_group_name = azurerm_resource_group.resource_group.name
location = azurerm_resource_group.resource_group.location
nics = tomap({
for k, subnet_id in module.SUBNETS.subnet_ids : k => {
name = var.niclist[k].name
subnet_id = subnet_id
}
})
}
SUBNETS.tf
resource "azurerm_subnet" "SUBNETS" {
for_each=var.Subnetlist
name=each.value.name
address_prefixes=[each.value.address]
resource_group_name = var.resource_group_name
virtual_network_name = var.virtual_network_name
}
output "subnet_ids" {
value = tomap({ for k, s in azurerm_subnet.SUBNETS : k => s.id })
}
NETWORKINTERFACE.tf
resource "azurerm_network_interface" "NETWORKINTERFACE" {
for_each=var.nics
name = each.value.name
location = var.location
resource_group_name = var.resource_group_name
ip_configuration {
name = "ipconfig1"
subnet_id = each.value.subnet_id
private_ip_address_allocation = "Dynamic"
}
}
When using for_each across multiple resources and modules like this it's important to preserve the tracking keys of the objects so that Terraform can see which instances of one resource correlate with instances of another and with the intermediate data structures that you use to pass the data around.
In your case, that would mean changing the shape of the subnet_ids output value to be a map from subnet key to subnet ID, rather than just the subnet IDs alone, so that it's clear which ID belongs to which subnet key:
resource "azurerm_subnet" "all" {
for_each = var.Subnetlist
name = each.value.name
address_prefixes = [each.value.address]
resource_group_name = var.resource_group_name
virtual_network_name = var.virtual_network_name
}
output "subnet_ids" {
value = tomap({ for k, s in azurerm_subnet.all : k => s.id })
}
Your network interface module seems to have an input variable niclist whose declaration you didn't show, but I'm assuming it'll look something like this, and I'm going to rename it to nics because I'm changing it to be a map instead:
variable "nics" {
type = map(
object({
name = string
subnet_id = string
})
)
}
You can then use that variable as the basis for your for_each of network interfaces:
resource "azurerm_network_interface" "all" {
for_each = var.nics
name = each.value.name
location = var.location
resource_group_name = var.resource_group_name
ip_configuration {
name = "ipconfig1"
subnet_id = each.value.subnet_id
private_ip_address_allocation = "Dynamic"
}
}
The remaining problem then is how to build the value of the nics input variable from inside the root module. You haven't shown how the root module's niclist variable is populated, and so I don't know how NICs and subnets are related. Since you showed a configuration which suggests one NIC per subnet, perhaps it would be sufficient to derive the nics variable value directly from the subnets map and remove var.niclist altogether:
module "network_interface" {
source = "./Modules/NIConfig"
resource_group_name = azurerm_resource_group.resource_group.name
location = azurerm_resource_group.resource_group.location
nics = tomap({
for k, subnet_id in module.subnets.subnet_ids : k => {
name = k
subnet_id = subnet_id
}
})
}
NOTE: The naming scheme you're currently using doesn't match usual Terraform idiom and so I changed some of the names in my answer here to use the standard style of all-lowercase names with words separated by underscores. I would suggest following the standard naming scheme so that your configuration will be intuitive to future maintainers who might already have Terraform experience from elsewhere.

Resources