Azure Subnet NSG Association using Terraform - 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.

Related

List & String Conversion Issue for Data sources

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]

Issue in creating DNS record for azure dnszones in terraform

Objective: Trying to create DNZ Zones and DNS records via terraform on Azure
Code that I tried:
My Variable.tf:
variable "subnets" {
description = "subnet address prefixes to respective environments"
type = any
default = {
dev = {
gw_snet = {
attach_nsg = false
virtual_network_name = "vnet-hub"
name = "GatewaySubnet"
address_prefixes = ["xx.xxx.x.x/xx"]
}
dns-snet = {
attach_nsg = false
virtual_network_name = "vnet-hub"
name = "DNSSubnet"
address_prefixes = ["xx.xxx.x.x/xx"]
}
common_snet = {
attach_nsg = false
virtual_network_name = "vnet-hub"
name = "CommonSubnet"
address_prefixes = ["xx.xxx.x.x/xx"]
}
clientdata_snet = {
attach_nsg = true
virtual_network_name = "vnet-hub"
name = "ClientDataSubnet"
address_prefixes = ["xx.xxx.x.x/xx"]
}
}
}
}
My main.tf:
resource "azurerm_private_dns_a_record" "monitor_snapshot" {
for_each = { for k, v in var.subnets[var.env] : k => v }
name = "snapshot"
zone_name = azurerm_private_dns_zone.monitor.name
resource_group_name = azurerm_resource_group.rg[0].name
ttl = 3600
records = [cidrhost(azurerm_subnet.mysubnet["gw_snet"].address_prefixes[0], 11)]
}
Issue that I am facing:
Since I used creating multiple subnets using for_each in loop, so when I try to create dns record using particular subnet , here in this case "gw_snet" but, terraform is erroring out as it tried to add dnsrecord for each subnet which I dont want.. only using gw_snet it has to create.
can you please suggest what mistake I am doing here ?

Creating Multiple subnet in azure

Varible.tf
variable "vnet" {
type = map(any)
description = "creating rg and vmet"
default = {
"rg1" = {
vnet_name = "vnet1"
address = ["10.0.0.0/16"]
subnet_name = ["snet1", "snet2"]
subnet_address = ["10.1.0.0/24", "10.2.0.0/24"]
location = "south india"
}
}
}
main.tf
resource "azurerm_subnet" "mysubnet" {
for_each = var.vnet
name = each.value["subnet_name"]
address_prefixes = each.value["subnet_address"]
address_prefixes = each.value["subnet_address"]
virtual_network_name = each.value["vnet_name"]
resource_group_name = each.key
}
Error:
Error: Incorrect attribute value type
on main.tf line 25, in resource "azurerm_subnet" "mysubnet":
name = each.value["subnet_name"]
each.value["subnet_name"] is tuple with 2 elements
Inappropriate value for attribute "name": string required.
How to iterate to create multiple subnet ?
You have to flatten your variable first. For example:
locals {
vnet_flat = merge([
for group_name, details in var.vnet:
{for idx in range(length(details.subnet_name)):
"${group_name}-${idx}" => {
group_name = group_name
vnet_name = details.vnet_name
address = details.address
subnet_name = details.subnet_name[idx]
subnet_address = details.subnet_address[idx]
location = details.location
}
}
]...)
}
resource "azurerm_subnet" "mysubnet" {
for_each = local.vnet_flat
name = each.value.subnet_name
address_prefixes = [each.value.subnet_address]
virtual_network_name = each.value.vnet_name
resource_group_name = each.value.group_name
}
The ... is for Expanding Function Arguments.

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

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

Terraform create multiple network interfaces with `for_each` and reference `azurerm_public_ip`

I have the following code:
resource "azurerm_public_ip" "imaged_pub_ip" {
for_each = var.create_vm_images ? var.vms_to_image : {}
name = "${var.team_name}_${var.release}_imaged_public_ip_${each.value}"
location = var.loc
resource_group_name = "${var.team_name}_${var.release}_${var.owner}_${var.intention}"
allocation_method = "Static"
tags = {
team = var.team_name
environment = var.env_name
useby = var.useby
release = var.release
devops_work_item_id = var.devops_work_item_id
owner = var.owner
intention = var.intention
}
}
resource "azurerm_network_interface" "imaged_net_int" {
for_each = var.create_vm_images ? var.vms_to_image : {}
name = "${var.team_name}_${var.release}_imaged_net_int_${each.value}"
location = var.loc
resource_group_name = "${var.team_name}_${var.release}_${var.owner}_${var.intention}"
ip_configuration {
name = "eth0"
private_ip_address_allocation = "Dynamic"
subnet_id = data.azurerm_subnet.subnet.id
public_ip_address_id = values(azurerm_public_ip.imaged_pub_ip).*.id
}
I am unable to reference the azurerm_public_ip.id in public_ip_address_id. It has an error: Inappropriate value for attribute "public_ip_address_id": string required.
I use a value map to say how many instances are required:
variable "vms_to_image" {
type = map
}
create_vm_images = "true"
vms_to_image = {
vm_id1 = "1"
vm_id2 = "0"
}
How do I reference the azurerm_public_ip.id for public_ip_address_id?
This worked public_ip_address_id = azurerm_public_ip.imaged_pub_ip[each.key].id

Resources