I am creating an Azure VNet using terraform, and creating a couple subnets in it. Later on , I want to create a network interface, and want to put it in one of the subnets already created for VNet. I do not know how to reference that subnet.
I tried below but it is now working:
subnet_id = "${azurerm_virtual_network.virtual-network.subnet.ServersSubnet.id}"
resource "azurerm_virtual_network" "virtual-network" {
name = "${var.ClientShortName}-az-network"
address_space = ["${local.AzureInfraNetwork}"]
location = "${var.resource-location}"
resource_group_name = "${azurerm_resource_group.test-resource-group.name}"
subnet {
name = "ServersSubnet"
address_prefix = "${local.ServersSubnet}"
}
subnet {
name = "GatewaySubnet"
address_prefix = "${local.GatewaySubnet}"
}
}
Error: Cannot index a set value
on main.tf line 120, in resource "azurerm_network_interface" "DCNIC":
120: subnet_id = "${azurerm_virtual_network.virtual-network.subnet.ServersSubnet.id}"
Block type "subnet" is represented by a set of objects, and set elements do
not have addressable keys. To find elements matching specific criteria, use a
"for" expression with an "if" clause.
Below is the complete solution.
If the subnets are created as blocks, you can reference a given subnet's resource ID as follows:
resource "azurerm_resource_group" "main" {
name = "vnet-rg"
location = "eastus"
}
resource "azurerm_virtual_network" "main" {
name = "my-vnet"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
address_space = ["10.0.0.0/16"]
subnet {
name = "subnet1"
address_prefix = "10.0.1.0/24"
}
subnet {
name = "subnet2"
address_prefix = "10.0.2.0/24"
}
subnet {
name = "subnet3"
address_prefix = "10.0.3.0/24"
}
}
output "subnet1_id" {
value = azurerm_virtual_network.main.subnet.*.id[0]
}
output "subnet2_id" {
value = azurerm_virtual_network.main.subnet.*.id[1]
}
output "subnet3_id" {
value = azurerm_virtual_network.main.subnet.*.id[2]
}
When creating subnets as blocks you must reference them using the list syntax, e.g.:
foo = azurerm_virtual_network.virtual-network.subnet[0].id
bar = azurerm_virtual_network.virtual-network.subnet[1].id
This is useful if the subnets form a pool of redundant resources and you don't care about referencing any subnet in particular.
I don't believe that's your case, so you might consider creating your subnets as separated resources, e.g:
resource "azurerm_virtual_network" "main" {
name = "${var.ClientShortName}-az-network"
address_space = [local.AzureInfraNetwork]
location = var.resource-location
resource_group_name = azurerm_resource_group.test-resource-group.name
}
resource "azurerm_subnet" "server" {
virtual_network_name = azurerm_virtual_network.main.name
name = "ServersSubnet"
address_prefix = local.ServersSubnet
}
resource "azurerm_subnet" "gateway" {
virtual_network_name = azurerm_virtual_network.main.name
name = "GatewaySubnet"
address_prefix = local.ServersSubnet
}
Then you could reference one of your subnets using the regular object attribute syntax:
foo = azurerm_subnet.server.id
Also note that I'm using terraform => 0.12 syntax, so I can write foo.bar instead of "${foo.bar}" when I don't need string interpolation.
If anyone else needs the answer to this use this:
"${element(azuread_application.events_backend.app_role[*].id,0)}"
You can break this out into creating the virtual network and the subnet as separate resources. Advantage of this is that you can return your subnets as a map rather than a list, making it easier to retrieve by name later on, it also makes it stable if you need to add/remove subnets at a later stage.
locals {
subnets = {
Servers = "10.0.1.0/24",
Gateway = "10.0.2.0/24"
}
}
resource "azurerm_resource_group" "main" {
name = "vnet-rg"
location = "eastus"
}
resource "azurerm_virtual_network" "main" {
name = "my-vnet"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
address_space = [ local.AzureInfraNetwork ]
}
resource "azurerm_subnet" "main" {
for_each = var.subnets
name = "${each.key}-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [ each.value ]
}
output "subnets" {
value = azurerm_subnet.main
}
Related
I need to fetch the subnetid from azurerm_subnet data resource as subnet is used in dynamic block of azurerm_virtual_network as map(object) type
resource "azurerm_virtual_network" "example" {
name = "example-vnet"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
dynamic "subnet" {
for_each = var.subnets
content {
name = subnet.value.name
address_prefix = subnet.value.address_prefix
security_group = azurerm_network_security_group.example[subnet.key].id
}
}
}
Fetch the second subnetid to attach it to storage account
resource "azurerm_storage_account" "example" {
count = length(var.subnets)
name = "storageaccountname"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
account_tier = "Standard"
account_replication_type = "GRS"
network_rules {
default_action = "Deny"
virtual_network_subnet_ids = ["${data.azurerm_subnet.subnetid.id}"]
}
}
Please can any one help to solve as i want to declare subnet in azurerm_virtual_network as dynamic block and get the subnet if from the data resource and please find my terraform.tfvars as below
subnets = {
subnet1 = {
name = "subnet1"
address_prefix = "10.0.0.0/24"
}
subnet2 = {
name = "subnet2"
address_prefix = "10.0.1.0/24"
}
subnet3 = {
name = "subnet3"
address_prefix = "10.0.2.0/24"
}
}
IMPORTANT
count = length(var.subnets) in resource "azurerm_storage_account" "example" {} is still there in your question which is logically incorrect as I have stated in the comments.
Answer
With your comments, I am assuming that you want to use id of subnet2 in network_rules of resource "azurerm_storage_account" "example" {}. With your current approach where creating subnets within the virtual network resource you have to use splat expressions and locals to make a map out of the set object and then can directly refer wherever is required.
While doing referencing even with locals and splat expressions it is still required to use the name of the subnet as it is not possible for terraform to know what you want without any data.
resource "azurerm_virtual_network" "example" {
name = "example-vnet"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
dynamic "subnet" {
for_each = var.subnets
content {
name = subnet.value.name
address_prefix = subnet.value.address_prefix
# security_group = azurerm_network_security_group.example[subnet.key].id ## I have ignored it as no relevant code is shared###
}
}
}
locals {
subnets = { for subnet in azurerm_virtual_network.example.subnet : subnet.name => subnet }
}
resource "azurerm_storage_account" "example" {
#count = length(var.subnets) ## Removed it too as logically incorrect with the current code ##
name = "storageaccountname"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
account_tier = "Standard"
account_replication_type = "GRS"
network_rules {
default_action = "Deny"
virtual_network_subnet_ids = [local.subnets.subnet2.id]
}
You do not need a data source when referencing the attributes from one resource/module to another resource/module in the root module.
However, I suggest using azurerm_subnet resource for easier reference in spite of creating subnets in the virtual network resource itself because your might need Microsoft.Storage service_endpoints in your subnets for working with storage account with network_rules.
Sorry if this is a dummy question but I'm new to terraform and I could find any documentation or anything that covers this scenario.
I have a Resource Group = rg1, which has a vnet = vnet1 and a subnet = subnet1.
I'm trying to create a new subnet (subnet2) in same vnet (vnet1), terraform is giving me the below error.
~ name = "subnet1" ->
"subnet2" # forces replacement
Plan: 1 to add, 0 to change, 1 to destroy.
Could anyone please let me know why terraform replaces the already created subnet? Is there a possible work around?
Edit 1
Apologies for not including the code, please find the same below;
main.tf
resource "azurerm_resource_group" "rg" {
name = var.rgname
location = var.rglocation
}
resource "azurerm_virtual_network" "vnet" {
name = var.vnetname
address_space = [var.vnet_address_space]
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
}
resource "azurerm_subnet" "vnet_subnets" {
name = var.subnet_name
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = [var.subnet_prefix]
}
Tfvars
rglocation = "westus"
rgname = "rg1"
vnetname = "vnet1"
vnet_address_space = "10.0.0.0/16"
subnet_name = "subnet2"
subnet_prefix = "10.0.2.0/24"
Variable.tf
variable "rglocation" {
}
variable "rgname" {
}
variable "vnetname" {
}
variable "vnet_address_space" {
}
variable "subnet_prefix" {
}
variable "subnet_name" {
}
Error Message
Terraform will perform the following actions:
# azurerm_subnet.vnet_subnets must be replaced
-/+ resource "azurerm_subnet" "vnet_subnets" {
~ address_prefix = "10.0.1.0/24" -> (known after apply)
~ address_prefixes = [
- "10.0.1.0/24",
+ "10.0.2.0/24",
]
~ id = "" -> (known after apply)
~ name = "subnet1" -> "subnet2" # forces replacement
- service_endpoint_policy_ids = [] -> null
- service_endpoints = [] -> null
# (4 unchanged attributes hidden)
}
Plan: 1 to add, 0 to change, 1 to destroy.
This happens because you are basically overwriting your TF files. So previous subnet gets destroyed for the new one. The proper way of re-using the same code for different resources is through workspaces or by having different setups in different folders fully separated.
But it seems to me that in your case you want to add new subnet, so you will have two of them at the end. In that case, you should use count or for_each. This way you will create two subnets using a loop.
For that your variables should be lists:
subnet_name = ["subnet1", "subnet2"]
subnet_prefix = ["10.0.1.0/24","10.0.2.0/24"]
then
resource "azurerm_subnet" "vnet_subnets" {
count = length(var.subnet_name)
name = var.subnet_name[count.index]
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = [var.subnet_prefix[count.index]]
}
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.
I am working on creating a platform on Azure using terraform and I have created a Vnet and 2 subnets by passing the variable to subnet creation using for_each. Now I have to reference these two subnets in my entire platform while crating different modules, like in aks cluster or application gateway. How can I pass this two subnets (may be I can change variable to three so it can be customized) to other modules and how can I refer them but identification which subnet I am using like with name is a good option.
My main.tf file
resource "azurerm_virtual_network" "vnet" {
name = var.vnet_name
location = var.location
resource_group_name = var.resource_group_name
address_space = [var.vnet_address_space]
tags = {
environment = "dev"
}
}
resource "azurerm_subnet" "subnet" {
for_each = {for subnet in var.subnets: subnet.prefix => subnet}
name = each.value.name
address_prefixes = [each.value.prefix]
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.vnet.name
}
variables are defined below.
resource_group_name ="Dev"
location ="South Central US"
vnet_name ="vnet"
vnet_address_space ="10.16.0.0/12"
subnets = [{
name ="DMZ"
prefix ="10.16.0.0/16"
},{
name ="Internal"
prefix ="10.17.0.0/16"
}
]
There are two conditions for you to use the subnets created before in other modules.
First, if you just use the code you provide in the root level with other modules, I assume you want to use the subnets in the aks module:
variable "vnet_name" {}
variable "resource_group_name" {}
variable "vnet_address_space" {}
variable "location" {}
variable "subnets" {}
resource "azurerm_virtual_network" "vnet" {
name = var.vnet_name
location = var.location
resource_group_name = var.resource_group_name
address_space = [var.vnet_address_space]
tags = {
environment = "dev"
}
}
resource "azurerm_subnet" "subnet" {
for_each = {for subnet in var.subnets: subnet.prefix => subnet}
name = each.value.name
address_prefixes = [each.value.prefix]
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.vnet.name
}
module "aks" {
source = "./modules/aks"
DMZ_subnet = azurerm_subnet.subnet["DMZ"]
Internal_subnet = azurerm_subnet.subnet["Internal"]
...
}
Second, if you put the network code in a module and you want to use the subnets in other modules, then you need to define the output in the network module and use the output in other modules:
./modules/vnet/main.tf
variable "vnet_name" {}
variable "resource_group_name" {}
variable "vnet_address_space" {}
variable "location" {}
variable "subnets" {}
resource "azurerm_virtual_network" "vnet" {
name = var.vnet_name
location = var.location
resource_group_name = var.resource_group_name
address_space = [var.vnet_address_space]
tags = {
environment = "dev"
}
}
resource "azurerm_subnet" "subnet" {
for_each = {for subnet in var.subnets: subnet.prefix => subnet}
name = each.value.name
address_prefixes = [each.value.prefix]
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.vnet.name
}
output "subnets" {
value = azurerm_subnets.subnet
}
main.tf
module "network" {
source = "./modules/network"
...
}
module "aks" {
source = "./modules/aks"
DMZ_subnet = module.network.subnets["DMZ"]
Internal_subnet = module.network.subnets["Internal"]
...
}
Update:
The above code uses the name of the subnet to quote the subnet, so you need to change the for_each in the azurerm_subnet into below and I also recommend this:
for_each = {for subnet in var.subnets: subnet.name => subnet}
With your origin code:
for_each = {for subnet in var.subnets: subnet.prefix=> subnet}
You need to use the subnet prefix to quote the subnet, like
DMZ_subnet = azurerm_subnet.subnet["10.16.0.0/16"]
the output of the subnet like this:
Example:
I want to create 2 vnets using variables and create 2 subnets one vnet and 3 subnets in one vnet.
For your requirement, the example code here:
variable "vnet_address_space" {
default = "10.0.0.0/16"
}
variable "subnet_prefix" {
default = [
"10.0.1.0/24",
"10.0.2.0/24"
]
}
resource "azurerm_resource_group" "test" {
name = "testResourceGroup1"
location = "East US"
}
resource "azurerm_virtual_network" "test" {
name = "acceptanceTestVirtualNetwork1"
address_space = ["${var.vnet_address_space}"]
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
}
resource "azurerm_subnet" "test" {
count = "${length(var.subnet_prefix)}"
name = "testsubnet-${count.index}"
resource_group_name = "${azurerm_resource_group.test.name}"
virtual_network_name = "${azurerm_virtual_network.test.name}"
address_prefix = "${element(var.subnet_prefix, count.index)}"
}
You can create another Vnet in the same format and change something in it as you wish.