How to handle empty list for dynamic block - terraform

This is regarding an Azure resource, app_service, but I think it’s a more general HCL question…
You can specify IP restrictions to an app_service using a dynamic block e.g.
locals {
ip_addresses = [ "192.168.250.1" ]
}
resource "azurerm_resource_group" "example" {
name = "example-resources"
location = "West Europe"
}
resource "azurerm_app_service_plan" "example" {
name = "example-appserviceplan"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
sku {
tier = "Standard"
size = "S1"
}
}
resource "azurerm_app_service" "example" {
name = "example-app-service"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
app_service_plan_id = azurerm_app_service_plan.example.id
site_config {
dotnet_framework_version = "v4.0"
scm_type = "LocalGit"
}
app_settings = {
"SOME_KEY" = "some-value"
}
connection_string {
name = "Database"
type = "SQLServer"
value = "Server=some-server.mydomain.com;Integrated Security=SSPI"
}
dynamic "ip_restriction" {
for_each = toset(local.ip_addresses)
content {
ip_address = each.value
}
}
}
However, to remove the restrictions you need to explicit assign ip_restriction to the empty list, i.e.
resource "azurerm_app_service" "example" {
...
ip_restriction = []
}
What I don’t see is how to do this conditionally - if I make two resources and have those conditional my app_service will be created/destroyed whereas I need it updated in place.

I'm afraid that the dynamic block does not support an empty list when using a conditional expression. Read more reference here.
After my validation, the conditional expression like for_each = var.some_variable == "" ? [] : [1] does not work when var.some_variable set to null but this could work seperately when for_each = var.some_variable and var.some_variable set to null.
So, in this case, as the answer from #rkm, you can use the for loop like this working sample for me.
variable "ip_restrictions" {
default = [
# {
# ip_address = "1.1.1.1/32"
# virtual_network_subnet_id = null
# subnet_id = null
# name = "aaa"
# priority = 110
# action = "Allow"
# },
# {
# ip_address = "2.2.2.2/32"
# virtual_network_subnet_id = null
# subnet_id = null
# name = "bbb"
# priority = 112
# action = "Allow"
# },
]
}
resource "azurerm_app_service" "example" {
name = "nn-example-app-service"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
app_service_plan_id = azurerm_app_service_plan.example.id
site_config {
ip_restriction = [
for s in var.ip_restrictions :
{
ip_address = s.ip_address
virtual_network_subnet_id = s.virtual_network_subnet_id
subnet_id = s.subnet_id
name = s.name
priority = s.priority
action = s.action
}
]
}
}

This is special terraform syntax called Attributes as Blocks. Resource arguments defined using nested block syntax implicitly define a fixed collection of objects and thus in order to specify zero objects, we should explicitly set empty list. And these two forms cannot be mixed.
With that said, terraform supports an argument syntax too (even though they recommend using block syntax for simple cases for readability):
example = [
for name in var.names: {
foo = name
}
]

Just in case anyone lands here looking for how to return a blank dynamic configuration block (instead of an input parameter like the OP), you can make the iteration list used by the for_each to be blank, and use conditionals in the content to set everything to null.
For example, if you have a dynamic inline_policy for AWS iam_role and you pass your inline json with a variable name policy_documents, you can first combine them into a single json document, then set a local variable to either a blank or what was passed in:
data "aws_iam_policy_document" "combined_policy" {
source_policy_documents = var.policy_documents
}
locals {
policy_documents = length(var.policy_documents) == 0 ? [""] : [data.aws_iam_policy_document.combined_policy.json]
}
Now you can use local.policy_documents as the iterator for the dynamic block and the blank ensures the dynamic block will always generate something. To produce a blank inline_policy, you can test if the iterator is blank and set everything to null.
resource "aws_iam_role" "default" {
... #(other parameters ommitted)
dynamic "inline_policy" {
for_each = local.policy_documents
content {
name = length(inline_policy.value) == 0 ? null : "custom_inline_policy"
policy = length(inline_policy.value) == 0 ? null : inline_policy.value
}
}
}
In the case where no inline policy is passed in, the dynamic block will generate a blank inline_policy {}:
+ resource "aws_iam_role" "default" {
+ arn = (known after apply)
+ assume_role_policy = jsonencode(
{
+ Statement = [
+ {
+ Action = "sts:AssumeRole"
+ Effect = "Allow"
+ Principal = {
+ Service = "ec2.amazonaws.com"
}
+ Sid = ""
},
]
+ Version = "2012-10-17"
}
)
+ create_date = (known after apply)
+ id = (known after apply)
+ inline_policy {}
}
I discovered how important a blank inline_policy is by accident when someone manually added an inline policy in the console, but the dynamic inline_policy did not notice it when I ran a terraform plan. If there was no inline_policy to start with, then the dynamic block never runs and so terraform doesn't check it. If you force the dynamic block to run even if the inline_policy is blank, terraform will ensure it is blank and delete the manually added inline policy. (Look here and here for terraform documentation and here for a good discussion at hashicorp about blank dyanamic blocks.)

Related

Removing region from Resource Group name

How would I go about removing the location code (aus) from the Resource Group creation.
from:
rg-d-lxr-aus-app1
to
rg-d-lxr-app1
I only want this to affect the only RG and not any other resources eg vnets/keyvaults etc
app1
locals.tf
locals {
full_env_code = format("%s-%s-%s", lower(var.environment_code), lower(var.deployment_code), lower(var.location_code))
}
resource_groups.tf
module "rg-app1" {
source = "git::ssh://git#ssh.dev.azure.com/*"
resource_group_name = format("rg-%s-%s", local.full_env_code, lower(var.name_suffix))
location = var.location
}
variables.tf
variable "location" {
description = "Location in which to deploy resources"
# default = "Australia Southeast"
}
variable "environment_code" {
description = "Environment code"
# default = "d"
}
variable "environment" {
description = "Environment"
# default = "d"
}
variable "deployment_code" {
description = "Deployment code"
# default = "d"
}
variable "location_code" {
description = "Location code"
# default = "d"
}
dev.tfvars
environment = "non-prod"
environment_code = "d"
deployment_code = "my org"
location_code = "aus"
location = "Australia Southeast"
name_suffix = "app1"
Output of TF Plan
Terraform will perform the following actions:
# module.rg-sharegate.azurerm_resource_group.rg will be created
+ resource "azurerm_resource_group" "rg" {
+ id = (known after apply)
+ location = "australiasoutheast"
+ name = "rg-d-lxr-aus-app1"
}
Plan: 1 to add, 0 to change, 0 to destroy.
I am thinking we need to modify the RG module and use the split command, but i'm not sure how to go about it
module "rg-app1" {
resource_group_name = format("rg-%s-%s", local.full_env_code, lower(var.name_suffix))
You can use a mixture of built-in Terraform functions to achieve what you're after.
Local Code to recreate a MRE for you:
locals {
rg_string = "rg-d-lxr-aus-app1"
// Use split by "-" to create an array so we can use contains()
is_rg = contains(split("-", local.rg_string), "rg")
final_rg_string = local.is_rg ? replace(local.rg_string, "aus-", "") : local.rg_string
not_rg_string = "blah-d-lxr-aus-app1"
is_not_rg = contains(split("-", local.not_rg_string), "rg")
final_not_rg_string = local.is_not_rg ? replace(local.not_rg_string, "aus-", "") : local.not_rg_string
}
Result using Terraform Console:
> local.is_rg
true
> local.final_rg_string
"rg-d-lxr-app1"
> local.is_not_rg
false
> local.final_not_rg_string
"blah-d-lxr-aus-app1"
Links:
https://www.terraform.io/language/functions/replace
https://www.terraform.io/language/functions/contains
https://www.terraform.io/language/functions/split
You'll likely not need to duplicate the logic but I did just for demonstrative purposes. For example you can just use your resource_group_name and create your true/false conditions. If you need more help with that please let me know.

Creating subnets and assinging rout tables to them

I am new to terraform and I'm trying to create a VPC with multiple subnets and adding route tables to all those subnets in a for loop manner.
VPC: 10.207.0.0/16
There's number_of_subnets which will create subnets like this: 10.207.x.0/24
This code works fine:
variable "region" {
default = "us-east-1"
}
variable "availability_zone" {
default = "us-east-1a"
}
variable "cidr_block" {
default = "207"
}
variable "number_of_subnets" {
default = 5
}
provider "aws" {
region = var.region
}
resource "aws_vpc" "test_vpc" {
cidr_block = "10.${var.cidr_block}.0.0/16"
instance_tenancy = "default"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "test_vpc_${var.cidr_block}"
}
}
resource "aws_subnet" "test_subnets" {
count = var.number_of_subnets
vpc_id = aws_vpc.test_vpc.id
cidr_block = "10.${var.cidr_block}.${count.index+1}.0/24" # start from x.x.1.0/24
availability_zone = var.availability_zone
map_public_ip_on_launch = false
tags = {
Name = "test_subnet_${var.cidr_block}_${count.index+1}"
}
}
Now if I try to add this code to the bottom of the same file (everything is in one file called main.tf) to get the subnets and add route table to each:
# get all subnet IDs
data "aws_subnets" "q_subnets" {
filter {
name = "vpc-id"
values = [aws_vpc.test_vpc.id]
}
}
# add route table to all subnets
resource "aws_route_table_association" "rt_assoc_subnet" {
depends_on = [aws_subnet.test_subnets]
for_each = toset(data.aws_subnets.q_subnets.ids)
subnet_id = each.value
route_table_id = aws_route_table.test_rt.id
}
and run terraform apply it will give this error:
invalid for_each argument...
The "for_each" value depends on resource attribute that cannot be deteremined until apply,...
which doesn't make scense. First create vpc, then subnet, then get all subnets...
I also tried depends_on and didn't help.
How would I write this to make it work?
Any help is appreciated.
UPDATE1:
I tried to use aws_subnet.test_subnets.*.id instead of data and it still gives depencendy error:
variable "region" {
default = "us-east-1"
}
variable "availability_zone" {
default = "us-east-1a"
}
variable "cidr_block" {
default = "207"
}
variable "number_of_subnets" {
default = 5
}
provider "aws" {
region = var.region
}
resource "aws_vpc" "test_vpc" {
cidr_block = "10.${var.cidr_block}.0.0/16"
instance_tenancy = "default"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "test_vpc_${var.cidr_block}"
}
}
resource "aws_route_table" "test_rt" {
vpc_id = aws_vpc.test_vpc.id
route = []
tags = {
Name = "test_rt_${var.cidr_block}"
}
}
resource "aws_subnet" "test_subnets" {
count = var.number_of_subnets
vpc_id = aws_vpc.test_vpc.id
cidr_block = "10.${var.cidr_block}.${count.index+1}.0/24" # start from x.x.1.0/24
availability_zone = var.availability_zone
map_public_ip_on_launch = false
tags = {
Name = "test_subnet_${var.cidr_block}_${count.index+1}"
}
}
output "subnets" {
value = aws_subnet.test_subnets.*.id
}
# add route table to all subnets
resource "aws_route_table_association" "rt_assoc_subnet" {
depends_on = [aws_subnet.test_subnets]
for_each = toset(aws_subnet.test_subnets.*.id)
subnet_id = each.value
route_table_id = aws_route_table.test_rt.id
}
is there another way to pass the subnets to aws_route_table_association without getting dependency error?
Since you are using count, it is very hard to make count work with for_each. It would be better to continue using count for route table association as well. If you decide to go down that route, the only change you need is:
resource "aws_route_table_association" "rt_assoc_subnet" {
count = var.number_of_subnets
subnet_id = aws_subnet.test_subnets.*.id[count.index]
route_table_id = aws_route_table.test_rt.id
}
This will work as intended. However, if you must use for_each I would suggest defining a variable that could be used with it in all the resources you are now using count. If you really want to use for_each with the current code, then you can use the -target option [1]:
terraform apply -target=aws_vpc.test_vpc -target=aws_route_table.test_rt -target=aws_subnet.test_subnets
When running this command, this will be shown in the command output:
│ Warning: Resource targeting is in effect
│
│ You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current
│ configuration.
│
│ The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when Terraform
│ specifically suggests to use it as part of an error message.
After the targeted resources are created, you could re-run terraform apply and it should create the route table associations.
[1] https://www.terraform.io/cli/commands/plan#target-address

Terraform Apply Error: Code="InvalidResourceName" Message="Resource name is invalid" When creating VPN-GW Module

I'm receiving the following error when executing the Terraform Apply Command.
│ Error: Creating/Updating Virtual Network Gateway: (Name "VPN-GW-HUB-dev" / Resource Group "RG-HUB-dev"): network.VirtualNetworkGatewaysClient#CreateOrUpdate: Failure sending request: StatusCode=400 -- Original Error: Code="InvalidResourceName" Message="Resource name is invalid. The name can be up to 80 characters long. It must begin with a word character, and it must end with a word character or with '_'. The name may contain word characters or '.', '-', '_'." Details=[]
│
│ with module.MOD-VPN-GW.azurerm_virtual_network_gateway.vpngw,
│ on Modules/10.VPN-GW/main.tf line 31, in resource "azurerm_virtual_network_gateway" "vpngw":
│ 31: resource "azurerm_virtual_network_gateway" "vpngw" {
What was I trying to do?
My Terraform code creates a complete Hub-and-Spoke infrastructure in Azure. It worked very well until I decided to modularise the VPN GW part.
My Terraform code structure is the usual format:
--Modules/{module-name}/main.tf
--main.tf
So within the {module-name} - I have a folder called:
10.VPN-GW\main.tf
What have I tried?
Well, I have tried to reformat the code, I double checked it against all other modules that worked OK. I have NO idea why this error is happening, and it has been 3 days now non stop debugging this.
The module file is here:
#------------------------------
# VPN GW MODULE FILE - ALso contains GW SUBNET
#-------------------------------
resource "azurerm_subnet" "gwSubnet" {
name = var.subnet_name
resource_group_name = var.subnet_resource_group_name
virtual_network_name = var.subnet_virtual_network_name
address_prefixes = var.subnet_address_prefixes
}
variable "subnet_name" {
type = string
}
variable "subnet_resource_group_name" {
type = string
}
variable "subnet_virtual_network_name" {
type = string
}
variable "subnet_address_prefixes" {
type = list(string)
}
output "outSubnetIDVPNGW" {
value = azurerm_subnet.gwSubnet.id
}
resource "azurerm_virtual_network_gateway" "vpngw" {
name = "${var.vpn-gw-name}"
location = "${var.vpn-gw-location}"
resource_group_name = "${var.vpngw_resource_group_name}"
type = "Vpn"
vpn_type = "RouteBased"
active_active = false
enable_bgp = false
sku = "VpnGw1"
ip_configuration {
name = "${var.ip_configuration_name}"
public_ip_address_id = "${var.ip_configuration_public_ip_address_id}"
private_ip_address_allocation = "Dynamic"
subnet_id = azurerm_subnet.gwSubnet.id
}
vpn_client_configuration {
address_space = "${var.vpn_client_configuration_address_space}"
root_certificate {
name = "${var.root_certificate_name}"
public_cert_data = "${var.cert}"
}
revoked_certificate {
name = "${var.revoked_certificate_name}"
thumbprint = "${var.revoked_certificate_thumbprint}"
}
}
}
variable "vpn-gw-name" {
type = string
//default = ""
}
variable "vpn-gw-location" {
//default = ""
type = string
}
variable "vpngw_resource_group_name" {
default = ""
}
variable "ip_configuration_name" {
default = ""
}
variable "ip_configuration_public_ip_address_id" {
default = ""
}
/*
variable "ip_configuration_private_ip_address_allocation" {
//default = ""
// type = string
}
*/
variable "vpn_client_configuration_address_space" {
default = []
}
variable "root_certificate_name" {
default = ""
}
variable "cert" {
default = ""
}
variable "revoked_certificate_name" {
default = ""
}
variable "revoked_certificate_thumbprint" {
default = ""
}
variable "ip_configuration" {
type = map(string)
}
variable "vpn_client_configuration" {
type = object({
address_space = list(string)
root_certificate = map(string)
revoked_certificate = map(string)
})
}
/*
variable "revoked_certificate" {
type = map(string)
}
*/
The main.tf file -- that calls each module, is given below - (I'm only adding the snippet for the part causing the error) - i.e. creation of the VPN GW
#-----------------------------------------
# Create GW SUBNET & VPN GW
#-----------------------------------------
module "MOD-VPN-GW" {
source = "./Modules/10.VPN-GW"
subnet_name = "GatewaySubnet"
subnet_resource_group_name = "RG-HUB-${var.environmentCode}"
subnet_virtual_network_name = "VNET-HUB-${var.environmentCode}"
subnet_address_prefixes = ["10.0.1.0/27"]
vpn-gw-name = "VPN-GW-HUB-${var.environmentCode}"
vpn-gw-location = "westeurope"
vpngw_resource_group_name = "RG-HUB-${var.environmentCode}"
ip_configuration = {
name = "VNetGatewayConfig"
public_ip_address_id = "${module.MOD-VPNGW-PIP.id}"
private_ip_address_allocation = "Dynamic"
subnet_id = module.MOD-VPN-GW.outSubnetIDVPNGW
}
vpn_client_configuration = {
address_space = ["172.16.10.0/24"]
root_certificate = {
name = "dev.vpn.macos.com"
public_cert_data = data.azurerm_key_vault_certificate.akv-certificate.certificate_data_base64
}
revoked_certificate = {
name = "Verizon-Global-Root-CA"
thumbprint = "912198EEF23DCAC40939312FEE97DD560BAE49B1"
}
}
depends_on = [
module.MOD-RG-HUB, module.MOD-VNET-HUB, azurerm_linux_virtual_machine.label-vm-spoke-01
]
}
What did I search for?
I cannot seem to find this particular error code in a google search. Its not telling me that name given to my VPN GW is incorrect - its giving me the name (in the error message) and telling me that there is no name given. Which is odd. Or have I misunderstood the error message?
The terraform init command succeeds.
The terraform validate command succeeds.
The terraform plan command succeeds.
This fails when applying
There is an issue between the root module and the VPN GW module. The way that modules in Terraform work is similar to functions in other programming languages: they require certain input variables which are then replaced by the values provided to them. More about modules in [1].
To refactor this code, I would do the following in the root module:
source = "./Modules/10.VPN-GW"
subnet_name = "GatewaySubnet"
subnet_resource_group_name = "RG-HUB-${var.environmentCode}"
subnet_virtual_network_name = "VNET-HUB-${var.environmentCode}"
subnet_address_prefixes = ["10.0.1.0/27"]
vpn-gw-name = "VPN-GW-HUB-${var.environmentCode}"
vpn-gw-location = "westeurope"
vpngw_resource_group_name = "RG-HUB-${var.environmentCode}"
ip_configuration_name = "VNetGatewayConfig"
ip_configuration_public_ip_address_id = module.MOD-VPNGW-PIP.id
vpn_client_configuration_address_space = ["172.16.10.0/24"]
root_certificate_name = "dev.vpn.macos.com"
cert = data.azurerm_key_vault_certificate.akv-certificate.certificate_data_base64
revoked_certificate_name = "Verizon-Global-Root-CA"
revoked_certificate_thumbprint = "912198EEF23DCAC40939312FEE97DD560BAE49B1"
}
As you can see, the only change I did was to assign the values to variables that the VPN GW module requires. The way this works is when the module is called, it will be provided with values for the variables and it will know what to do with them. There is no need to define the same blocks in the calling (root) and called module (VPN GW). So, think of the root module as of a piece of code that invokes a function, something like:
MOD_VPN_GW(subnet_name, subnet_resource_group_name, subnet_virtual_network_name, subnet_address_prefixes, vpn-gw-name, vpn-gw-location, vpngw_resource_group_name, ip_configuration_name, ip_configuration_public_ip_address_id, vpn_client_configuration_address_space, root_certificate_name, cert, revoked_certificate_name, revoked_certificate_thumbprint)
As for your question about how the subnet_id will be accessible, that is the easy part. In the VPN GW module there is a resource block for azurerm_subnet:
resource "azurerm_subnet" "gwSubnet" {
name = var.subnet_name
resource_group_name = var.subnet_resource_group_name
virtual_network_name = var.subnet_virtual_network_name
address_prefixes = var.subnet_address_prefixes
}
One of the attributes that is provided by this resource after creation is the subnet ID [2].That means that you can assign the attribute value by referencing the resulting value using this syntax [3]:
<resource type>.<resource_name>.<attribute>
In your case that is:
azurerm_subnet.gwSubnet.id
This value is already referenced in the subnet_id argument of the azurerm_virtual_network_gateway resource:
subnet_id = azurerm_subnet.gwSubnet.id
This means it is not required as one of the input variables by the module. The good thing is that the VPN GW module will know in which order to create resource as this is an implicit dependency.
[1] https://www.terraform.io/language/modules/develop
[2] https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet#id
[3] https://www.terraform.io/language/expressions/references#references-to-resource-attributes

Iterate over nested data with for / for_each at resource level

I am trying to work out how to iterate over nested variables from a complex object given in the following tfvars file using Terraform 0.12.10:
example.tfvars
virtual_network_data = {
1 = {
product_instance_id = 1
location = "somewhere"
address_space = ["192.168.0.0/23"]
dns_servers = []
custom_tags = {"test":"test value"}
subnets = [
{
purpose = "mgmt"
newbits = 4
item = 0
},
{
purpose = "transit"
newbits = 4
item = 1
}
]
}
}
example.tf
variable "virtual_network_data" {} #Data comes from example.tfvars
variable "resource_group_name" {
default = "my_resource_group"
}
variable "virtual_network_name" {
default = "my_virtual_network"
}
####
resource "azurerm_subnet" "pool" {
for_each = var.virtual_network_data
name = format("%s%s%02d", "subnet_", s.purpose, s.item)
resource_group_name = var.resource_group_name
virtual_network_name = var.virtual_network_name
address_prefix = cidrsubnet(each.value["address_space"], s.newbits, s.item)
}
In example.tf I can use each.value["address_space"] to get to the top level variables, but I can't work out how to get to the items in subnets (s.purpose, s.item & s.newbits).
I have used dynamic blocks, as part of a parent resource (below), which works but in this case, I need to move the subnet into its own resource. Simply put, how do I get the first for_each to behave like the second for_each in the dynamic block?
resource "azurerm_virtual_network" "pool" {
for_each = var.virtual_network_data
name = format("%s%02d", local.resource_name, each.key)
resource_group_name = var.resource_group_name
location = each.value["location"]
address_space = each.value["address_space"]
dns_servers = each.value["dns_servers"]
tags = merge(local.tags, each.value["custom_tags"])
dynamic "subnet" {
for_each = [for s in each.value["subnets"]: {
name = format("%s%s%02d", "subnet_", s.purpose, s.item)
prefix = cidrsubnet(element(each.value["address_space"],0), s.newbits, s.item)
}]
content {
name = subnet.value.name
address_prefix = subnet.value.prefix
}
}
}
Cheeky bonus, is there a way to replace s.item with something like each.key or count.index?
TIA
The technique in this situation is to use other Terraform language features to transform your collection to be a suitable shape for the for_each argument: one element per resource instance.
For nested data structures, you can use flatten in conjunction with two or more for expressions to produce a flat data structure with one element per nested object:
locals {
network_subnets = flatten([
for network_key, network in var.virtual_network_data : [
for subnet in network.subnets : {
network_key = network_key
purpose = subnet.purpose
parent_cidr_block = network.address_space[0]
newbits = subnet.newbits
item = subnet.item
}
]
])
}
Then you can use local.network_subnets as the basis for repetition:
resource "azurerm_subnet" "pool" {
# Each instance must have a unique key, so we'll construct one
# by combining the network key, the subnet "purpose", and the "item".
for_each = {
for ns in local.network_subnets : "${ns.network_key}.${ns.purpose}${ns.item}" => ns
}
name = format("%s%s%02d", "subnet_", each.value.purpose, each.value.item)
resource_group_name = var.resource_group_name
virtual_network_name = var.virtual_network_name
address_prefix = cidrsubnet(each.value.parent_cidr_block, each.value.newbits, each.value.item)
}
There's a similar example in the flatten documentation, as some additional context.
As alternative to flatten trick, you may for_each resource by first parameter inside nested module, then for_each this module by second parameter.

Variable in terraform resource referance

I'm trying create a resource in terraform that will create a number of subnets based on a list variable.
I'm having trouble with references to existing resources. For example in the following code network_security_group_id is hardcoded to azurerm_network_security_group.k8s.id:
variable "resources_large" {
description = "List of Large Networks"
default = [
"k8s",
"storm"
]
}
resource "azurerm_subnet" "large" {
name = "ue-${var.environment}-${var.resources_large[count.index]}-subnet-${replace("${cidrsubnet("${local.subnet_ranges["large"]}", "${var.newbit_size["large"] }", count.index )}", "/[./]/", "-" ) }"
resource_group_name = "ue-${var.environment}-${var.resources_large[count.index]}-rg"
virtual_network_name = "${azurerm_virtual_network.dev.name}"
address_prefix = "${cidrsubnet("${local.subnet_ranges["large"]}", "${var.newbit_size["large"] }", count.index )}"
network_security_group_id = "${azurerm_network_security_group.k8s.id}"
count = "${length(var.resources_large)}"
depends_on = ["azurerm_virtual_network.dev"]
}
This needs to reference existing security groups based on the name in the resources_large list.
What I'd like to have is something which looks likes this:
network_security_group_id = "${azurerm_network_security_group.${var.resources_large[count.index]}.id}"
Which doesn't work, I'm guessing due to the lack of variable interpolation support.
Is there any way to reference other resources based on variable?
Maybe something like this
locals {
sgs = {
k8s = "${azurerm_network_security_group.k8s.id}"
storm = "${azurerm_network_security_group.storm.id}"
}
}
...
network_security_group_id = "${lookup( locals.sgs, var.resources_large[count.index])}"
may work.
If you create the SG using the same counter, it can be just
network_security_group_id = "${element(azurerm_network_security_group.*.id, count.index)}"
HTH

Resources