In its simplest form,
main.tf is as below:
data "azurerm_resource_group" "tf-rg-external" {
name = var.rg_name
}
# Reference existing Virtual Network
data "azurerm_virtual_network" "tf-vn" {
name = var.vnet_name
resource_group_name = data.azurerm_resource_group.tf-rg-external.name
}
# Reference existing subnet
data "azurerm_subnet" "tf-sn" {
name = var.subnet_name
virtual_network_name = data.azurerm_virtual_network.tf-vn.name
resource_group_name = data.azurerm_resource_group.tf-rg-external.name
}
resource "azurerm_network_security_group" "tf-nsg" {
name = var.app_nsg
location = data.azurerm_virtual_network.tf-vn.location
resource_group_name = data.azurerm_resource_group.tf-rg-external.name
}
resource "azurerm_network_security_rule" "tf-nsr-5986" {
name = "Open Port 5986"
priority = 101
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "5986"
source_address_prefixes = var.allowed_source_ips
destination_address_prefix = "VirtualNetwork"
resource_group_name = data.azurerm_resource_group.tf-rg-external.name
network_security_group_name = azurerm_network_security_group.tf-nsg.name
}
resource "azurerm_network_security_rule" "tf-nsr-3389" {
name = "Open Port 3389"
priority = 102
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "3389"
source_address_prefixes = var.allowed_source_ips
destination_address_prefix = "VirtualNetwork"
resource_group_name = data.azurerm_resource_group.tf-rg-external.name
network_security_group_name = azurerm_network_security_group.tf-nsg.name
}
# Assosciate NSG to subnet
resource "azurerm_subnet_network_security_group_association" "tf-snnsg" {
subnet_id = data.azurerm_subnet.tf-sn.id
network_security_group_id = azurerm_network_security_group.tf-nsg.id
}
# Network inteface for Interface
resource "azurerm_network_interface" "tf-ni" {
count = var.vm_count
name = "${var.base_hostname}${format("%02d", count.index + 1)}-nic01"
location = data.azurerm_virtual_network.tf-vn.location
resource_group_name = data.azurerm_resource_group.tf-rg-external.name
ip_configuration {
name = "${var.base_hostname}${format("%02d", count.index)}-iip01"
subnet_id = data.azurerm_subnet.tf-sn.id
private_ip_address_allocation = "dynamic"
public_ip_address_id = element(azurerm_public_ip.tf-pip.*.id, count.index)
}
}
resource "azurerm_public_ip" "tf-pip" {
count = var.vm_count
location = data.azurerm_virtual_network.tf-vn.location
name = "${var.base_hostname}${format("%02d", count.index + 1)}-pip01"
resource_group_name = data.azurerm_resource_group.tf-rg-external.name
allocation_method = "Dynamic"
}
# Storage Account
resource "azurerm_storage_account" "tf-sa" {
count = var.vm_count
name = "${lower(var.base_hostname)}${format("%02d", count.index + 1)}${var.sto_acc_suffix}01"
location = data.azurerm_virtual_network.tf-vn.location
resource_group_name = data.azurerm_resource_group.tf-rg-external.name
account_tier = var.sto_acc_tier_std
account_replication_type = var.sto_acc_rep_type_lrs
}
resource "azurerm_virtual_machine" "tf-vm" {
count = var.vm_count
name = "${var.base_hostname}${format("%02d", count.index + 1)}"
location = data.azurerm_virtual_network.tf-vn.location
resource_group_name = data.azurerm_resource_group.tf-rg-external.name
network_interface_ids = [element(azurerm_network_interface.tf-ni.*.id, count.index)]
vm_size = var.vm_size
delete_os_disk_on_termination = true
delete_data_disks_on_termination = true
storage_image_reference {
publisher = var.vm_publisher
offer = var.vm_offer
sku = var.vm_sku
version = var.vm_img_version
}
storage_os_disk {
name = "${var.base_hostname}${format("%02d", count.index + 1)}-wosdsk01"
caching = var.caching_option
create_option = var.create_option
managed_disk_type = var.managed_disk_std_lrs
}
os_profile {
computer_name = "${var.base_hostname}${format("%02d", count.index + 1)}"
admin_username = var.username
admin_password = var.password
}
os_profile_windows_config {
enable_automatic_upgrades = false
provision_vm_agent = "true"
}
}
variables.tf is below:
# Declare env variable
variable "rg_name" {
type = string
}
variable "vnet_name" {
type = string
}
variable "subnet_name" {
type = string
}
variable "app_nsg" {
type = string
}
variable "vm_count" {
type = number
}
variable "base_hostname" {
type = string
}
variable "sto_acc_suffix" {
type = string
}
variable "sto_acc_tier_std" {
type = string
default = "Standard"
}
variable "sto_acc_rep_type_lrs" {
type = string
default = "LRS"
}
variable "vm_size" {
type = string
}
variable "vm_publisher" {
type = string
}
variable "vm_offer" {
type = string
}
variable "vm_sku" {
type = string
}
variable "vm_img_version" {
type = string
}
variable "username" {
type = string
}
variable "password" {
type = string
}
variable "caching_option" {
type = string
default = "ReadWrite"
}
variable "create_option" {
type = string
default = "FromImage"
}
variable "managed_disk_std_lrs" {
type = string
default = "Standard_LRS"
}
variable "managed_disk_prem_lrs" {
type = string
default = "Premium_LRS"
}
variable "allowed_source_ips" {
description = "List of ips from which inbound connection to VMs is allowed"
type = list(string)
}
I run below command for upgrading terraform config to 0.12 and above
terraform 0.12upgrade
Error:
Error: Syntax error in configuration file
on main.tf line 22, in data "azurerm_resource_group" "tf-rg-external":
22: name = var.rg_name
Error while parsing: At 22:10: Unknown token: 22:10 IDENT var.rg_name
Error: Syntax error in configuration file
on variable.tf line 3, in variable "rg_name":
3: type = string
Error while parsing: At 3:10: Unknown token: 3:10 IDENT string
Any idea, what is the problem? this would work if I don't run terrafom 0.12upgrade command. It is intriguing me why it is not working. I did same upgrade command in another terraform config and I get similar error there.
One observation. This IDENT error comes for first variable in main.tf and variable.tf file. Unable to correlate this error.
You're already using 0.12 syntax, the 0.12upgrade command expects to find 0.11 syntax and will attempt to automatically update it.
e.g. name = var.rg_name - note the lack of ${}
See https://www.terraform.io/docs/commands/0.12upgrade.html
The terraform 0.12upgrade command applies several automatic upgrade rules to help prepare a module that was written for Terraform v0.11 to be used with Terraform v0.12.
(Emphasis mine)
Related
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]
I'm following Neal Shah's instructions for deploying multiple VMs with multiple managed disks (https://www.nealshah.dev/posts/2020/05/terraform-for-azure-deploying-multiple-vms-with-multiple-managed-disks/#deploying-multiple-vms-with-multiple-datadisks)
everything works fine except for the azurerm_virtual_machine_data_disk_attachment resource which fails with the following error
│ Error: Invalid index
│
│ on main.tf line 103, in resource "azurerm_virtual_machine_data_disk_attachment" "managed_disk_attach":
│ 103: virtual_machine_id = azurerm_linux_virtual_machine.vms[element(split("_", each.key), 1)].id
│ ├────────────────
│ │ azurerm_linux_virtual_machine.vms is tuple with 3 elements
│ │ each.key is "datadisk_dca0-apache-cassandra-node0_disk00"
│
│ The given key does not identify an element in this collection value: a number is required.
my code is below:
locals {
vm_datadiskdisk_count_map = { for k in toset(var.nodes) : k => var.data_disk_count }
luns = { for k in local.datadisk_lun_map : k.datadisk_name => k.lun }
datadisk_lun_map = flatten([
for vm_name, count in local.vm_datadiskdisk_count_map : [
for i in range(count) : {
datadisk_name = format("datadisk_%s_disk%02d", vm_name, i)
lun = i
}
]
])
}
# create resource group
resource "azurerm_resource_group" "resource_group" {
name = format("%s-%s", var.dca, var.name)
location = var.location
}
# create availability set
resource "azurerm_availability_set" "vm_availability_set" {
name = format("%s-%s-availability-set", var.dca, var.name)
location = azurerm_resource_group.resource_group.location
resource_group_name = azurerm_resource_group.resource_group.name
}
# create Security Group to access linux
resource "azurerm_network_security_group" "linux_vm_nsg" {
name = format("%s-%s-linux-vm-nsg", var.dca, var.name)
location = azurerm_resource_group.resource_group.location
resource_group_name = azurerm_resource_group.resource_group.name
security_rule {
name = "AllowSSH"
description = "Allow SSH"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
# associate the linux NSG with the subnet
resource "azurerm_subnet_network_security_group_association" "linux_vm_nsg_association" {
subnet_id = "${data.azurerm_subnet.subnet.id}"
network_security_group_id = azurerm_network_security_group.linux_vm_nsg.id
}
# create NICs for apache cassandra hosts
resource "azurerm_network_interface" "vm_nics" {
depends_on = [azurerm_subnet_network_security_group_association.linux_vm_nsg_association]
count = length(var.nodes)
name = format("%s-%s-nic${count.index}", var.dca, var.name)
location = azurerm_resource_group.resource_group.location
resource_group_name = azurerm_resource_group.resource_group.name
ip_configuration {
name = format("%s-%s-apache-cassandra-ip", var.dca, var.name)
subnet_id = "${data.azurerm_subnet.subnet.id}"
private_ip_address_allocation = "Dynamic"
}
}
# create apache cassandra VMs
resource "azurerm_linux_virtual_machine" "vms" {
count = length(var.nodes)
name = element(var.nodes, count.index)
location = azurerm_resource_group.resource_group.location
resource_group_name = azurerm_resource_group.resource_group.name
network_interface_ids = [element(azurerm_network_interface.vm_nics.*.id, count.index)]
availability_set_id = azurerm_availability_set.vm_availability_set.id
size = var.vm_size
admin_username = var.admin_username
disable_password_authentication = true
admin_ssh_key {
username = var.admin_username
public_key = var.ssh_pub_key
}
source_image_id = var.source_image_id
os_disk {
caching = "ReadWrite"
storage_account_type = var.storage_account_type
disk_size_gb = var.os_disk_size_gb
}
}
# create data disk(s) for VMs
resource "azurerm_managed_disk" "managed_disk" {
for_each= toset([for j in local.datadisk_lun_map : j.datadisk_name])
name= each.key
location = azurerm_resource_group.resource_group.location
resource_group_name = azurerm_resource_group.resource_group.name
storage_account_type = var.storage_account_type
create_option = "Empty"
disk_size_gb = var.disk_size_gb
}
resource "azurerm_virtual_machine_data_disk_attachment" "managed_disk_attach" {
for_each = toset([for j in local.datadisk_lun_map : j.datadisk_name])
managed_disk_id = azurerm_managed_disk.managed_disk[each.key].id
virtual_machine_id = azurerm_linux_virtual_machine.vms[element(split("_", each.key), 1)].id
lun = lookup(local.luns, each.key)
caching = "ReadWrite"
}
anyone know how to accomplish this? thanks!
I've tried several different approaches to this but have been unsuccessful so far, I was expecting it to work as described in Neal's post
I was able to get this working. However, I have not tested adding/removing nodes/disks yet. But this working to create multiple VMs with multiple data disks attached to each VM.
I use a variable file that I source to substitute the variables in the *.tf files.
variables.tf
variable "azure_subscription_id" {
type = string
description = "Azure Subscription ID"
default = ""
}
variable "dca" {
type = string
description = "datacenter [dca0|dca2|dca4|dca6]."
default = ""
}
variable "location" {
type = string
description = "Location of the resource group."
default = ""
}
variable "resource_group" {
type = string
description = "resource group name."
default = ""
}
variable "subnet_name" {
type = string
description = "subnet name"
default = ""
}
variable "vnet_name" {
type = string
description = "vnet name"
default = ""
}
variable "vnet_rg" {
type = string
description = "vnet resource group"
default = ""
}
variable "vm_size" {
type = string
description = "vm size"
default = ""
}
variable "os_disk_size_gb" {
type = string
description = "vm os disk size gb"
default = ""
}
variable "data_disk_size_gb" {
type = string
description = "vm data disk size gb"
default = ""
}
variable "admin_username" {
type = string
description = "admin user name"
default = ""
}
variable "ssh_pub_key" {
type = string
description = "public key for admin user"
default = ""
}
variable "source_image_id" {
type = string
description = "image id"
default = ""
}
variable "os_disk_storage_account_type" {
type = string
description = ""
default = ""
}
variable "data_disk_storage_account_type" {
type = string
description = ""
default = ""
}
variable "vm_list" {
type = map(object({
hostname = string
}))
default = {
vm0 ={
hostname = "${dca}-${name}-node-0"
},
vm1 = {
hostname = "${dca}-${name}-node-1"
}
vm2 = {
hostname = "${dca}-${name}-node-2"
}
}
}
variable "disks_per_instance" {
type = string
description = ""
default = ""
}
terraform.tfvars
# subscription
azure_subscription_id = "${azure_subscription_id}"
# name and location
resource_group = "${dca}-${name}"
location = "${location}"
dca = "${dca}"
# Network
subnet_name = "${subnet_name}"
vnet_name = "${dca}vnet"
vnet_rg = "th-${dca}-vnet"
# VM
vm_size = "${vm_size}"
os_disk_size_gb = "${os_disk_size_gb}"
os_disk_storage_account_type = "${os_disk_storage_account_type}"
source_image_id = "${source_image_id}"
# User/key info
admin_username = "${admin_username}"
ssh_pub_key = ${ssh_pub_key}
# data disk info
data_disk_storage_account_type = "${data_disk_storage_account_type}"
data_disk_size_gb = "${data_disk_size_gb}"
disks_per_instance= "${disks_per_instance}"
main.tf
# set locals for multi data disks
locals {
vm_datadiskdisk_count_map = { for k, query in var.vm_list : k => var.disks_per_instance }
luns = { for k in local.datadisk_lun_map : k.datadisk_name => k.lun }
datadisk_lun_map = flatten([
for vm_name, count in local.vm_datadiskdisk_count_map : [
for i in range(count) : {
datadisk_name = format("datadisk_%s_disk%02d", vm_name, i)
lun = i
}
]
])
}
# create resource group
resource "azurerm_resource_group" "resource_group" {
name = format("%s", var.resource_group)
location = var.location
}
# create data disk(s)
resource "azurerm_managed_disk" "managed_disk" {
for_each = toset([for j in local.datadisk_lun_map : j.datadisk_name])
name = each.key
location = azurerm_resource_group.resource_group.location
resource_group_name = azurerm_resource_group.resource_group.name
storage_account_type = var.data_disk_storage_account_type
create_option = "Empty"
disk_size_gb = var.data_disk_size_gb
}
# create availability set
resource "azurerm_availability_set" "vm_availability_set" {
name = format("%s-availability-set", var.resource_group)
location = azurerm_resource_group.resource_group.location
resource_group_name = azurerm_resource_group.resource_group.name
}
# create Security Group to access linux
resource "azurerm_network_security_group" "linux_vm_nsg" {
name = format("%s-linux-vm-nsg", var.resource_group)
location = azurerm_resource_group.resource_group.location
resource_group_name = azurerm_resource_group.resource_group.name
security_rule {
name = "AllowSSH"
description = "Allow SSH"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
# associate the linux NSG with the subnet
resource "azurerm_subnet_network_security_group_association" "linux_vm_nsg_association" {
subnet_id = "${data.azurerm_subnet.subnet.id}"
network_security_group_id = azurerm_network_security_group.linux_vm_nsg.id
}
# create NICs for vms
resource "azurerm_network_interface" "nics" {
depends_on = [azurerm_subnet_network_security_group_association.linux_vm_nsg_association]
for_each = var.vm_list
name = "${each.value.hostname}-nic"
location = azurerm_resource_group.resource_group.location
resource_group_name = azurerm_resource_group.resource_group.name
ip_configuration {
name = format("%s-proxy-ip", var.resource_group)
subnet_id = "${data.azurerm_subnet.subnet.id}"
private_ip_address_allocation = "Dynamic"
}
}
# create VMs
resource "azurerm_linux_virtual_machine" "vms" {
for_each = var.vm_list
name = each.value.hostname
location = azurerm_resource_group.resource_group.location
resource_group_name = azurerm_resource_group.resource_group.name
network_interface_ids = [azurerm_network_interface.nics[each.key].id]
availability_set_id = azurerm_availability_set.vm_availability_set.id
size = var.vm_size
source_image_id = var.source_image_id
custom_data = filebase64("cloud-init.sh")
admin_username = var.admin_username
disable_password_authentication = true
admin_ssh_key {
username = var.admin_username
public_key = var.ssh_pub_key
}
os_disk {
caching = "ReadWrite"
storage_account_type = var.os_disk_storage_account_type
disk_size_gb = var.os_disk_size_gb
}
}
# attache data disks vms
resource "azurerm_virtual_machine_data_disk_attachment" "managed_disk_attach" {
for_each = toset([for j in local.datadisk_lun_map : j.datadisk_name])
managed_disk_id = azurerm_managed_disk.managed_disk[each.key].id
virtual_machine_id = azurerm_linux_virtual_machine.vms[element(split("_", each.key), 1)].id
lun = lookup(local.luns, each.key)
caching = "ReadWrite"
}
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 ;-)
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
}
}
My terraform snippet:
variable "machine_details" {
type = object({
name = string
size = string
username = string
password = string
})
default = [
{
name = "example-vm"
size = "Standard_F2"
username = "adminuser"
password = "Notallowed1!"
}
]
}
I am getting error as below.
Error: Invalid default value for variable
│
│ on variables.tf line 38, in variable "machine_details":
│ 38: default = [
│ 39: {
│ 40: name = "example-vm"
│ 41: size = "Standard_F2"
│ 42: username = "adminuser"
│ 43: password = "Notallowed1!"
│ 44: }
│ 45: ]
This default value is not compatible with the variable's type constraint: object required.
I tried map(string) but didn't work too.
similary list(string) also.
I am trying the latest azurerm provider.
Also, in the gcp, we have option to provide count(for instances), so if I provide 2, two instances will be created.
How to do the same with azure and aws?
How to resolve this?
it is working this way.
variable "machine_details" {
type = object({
name = string
size = string
username = string
password = string
})
default = {
name = "example-vm"
size = "Standard_F2"
username = "adminuser"
password = "Notallowed1!"
}
}
And can refer like this : var.machine_details.name
As Marko E and luk2302 have mentioned , You have to declare and pass the variable as below :
variable "machine_details" {
type = list(object({
name = string
size = string
username = string
password = string
}))
default = [
{
name = "example-vm"
size = "Standard_F2"
username = "adminuser"
password = "Notallowed1!"
},
{
name = "example2-vm"
size = "Standard_F2"
username = "adminuser1"
password = "Notallowed2!"
}
]
}
So that if you want to use count then you can use something like below :
provider "azurerm" {
features{}
}
data "azurerm_resource_group" "example" {
name = "ansumantest"
}
resource "azurerm_virtual_network" "example" {
name = "example-network"
address_space = ["10.0.0.0/16"]
location = data.azurerm_resource_group.example.location
resource_group_name = data.azurerm_resource_group.example.name
}
resource "azurerm_subnet" "example" {
name = "internal"
resource_group_name = data.azurerm_resource_group.example.name
virtual_network_name = azurerm_virtual_network.example.name
address_prefixes = ["10.0.2.0/24"]
}
resource "azurerm_public_ip" "example" {
count = length(var.machine_details)
name = "aks-nfs-public-ip${count.index}"
location = data.azurerm_resource_group.example.location
resource_group_name = data.azurerm_resource_group.example.name
allocation_method = "Dynamic"
tags = {
environment = "Production"
}
}
resource "azurerm_network_interface" "example" {
count = length(var.machine_details)
name = "example-nic-${count.index}"
location = data.azurerm_resource_group.example.location
resource_group_name = data.azurerm_resource_group.example.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.example.id
public_ip_address_id = "${element(azurerm_public_ip.example.*.id, count.index)}"
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_ssh_public_key" "example" {
name = "ansuman-sshkey"
resource_group_name = data.azurerm_resource_group.example.name
location = data.azurerm_resource_group.example.location
public_key = file("~/.ssh/id_rsa.pub")
}
resource "azurerm_linux_virtual_machine" "example" {
count = length(var.machine_details)
name = var.machine_details[count.index].name
resource_group_name = data.azurerm_resource_group.example.name
location = data.azurerm_resource_group.example.location
size = var.machine_details[count.index].size
admin_username = var.machine_details[count.index].username
admin_password = var.machine_details[count.index].password
disable_password_authentication = true
network_interface_ids = ["${element(azurerm_network_interface.example.*.id, count.index)}"]
admin_ssh_key {
username = var.machine_details[count.index].username
public_key = azurerm_ssh_public_key.example.public_key
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
}
Output: