I am trying to make a Terraform playbook that creates Azure routes through a loop.
The end-goal is the user will type in their destinations in a comma separated list. For example:
Enter route destinations: 0.0.0.0/0,192.168.0.0/16
From this variable, the azurerm_route will run through this function and create the routes.
variable "destinations" {
default = "0.0.0.0/0,192.168.0.0/16"
}
resource "azurerm_route" "route" {
name = "route-${count.index}"
resource_group_name = "resourcegroup"
route_table_name = "table"
address_prefix = "${split(",", var.destinations)}[count.index]"
next_hop_type = "Internet"
count = "${length(split(",", var.destinations))}"
}
However, I am having an issue with counting through the prefix list and I am receiving the following error:
* azurerm_route.route[1]: At column 1, line 1: output of an HIL
expression must be a string, or a single list (argument 1 is
TypeList) in:
${split(",", var.destinations)}[count.index]
* azurerm_route.route[0]: At column 1, line 1: output of an HIL
expression must be a string, or a single list (argument 1 is
TypeList) in:
${split(",", var.destinations)}[count.index]
Figured it out. You have to use the element interpolation.
resource "azurerm_route" "route" {
name = "route-${count.index}"
resource_group_name = "resourcegroup"
route_table_name = "table"
address_prefix = "${element(split(",",var.destinations),count.index)}"
next_hop_type = "Internet"
count = "${length(split(",",var.destinations))}"
}
Related
I need to put a specific description of a csv file to each group I created.
On my csv, I have 2 columns: 1 for the group name and another for the description.
locals {
Right_Groups = [for x in csvdecode(file("${path.module}/_RightGroups.csv")) : x.droits_groups]
}
I create each group with :
resource "azuread_group" "Terra-Aad-Group-Right" {
for_each = toset(local.Right_Groups)
display_name = lower(each.value)
security_enabled = true
description = each.value
}
With this, the description is equal to the group name.
If I set "each.value.description" to "description", it doesn't work.
The csv is like this :
droits_groups,description
con-inf-dev01,dev01
con-axw-rec01,rec01
Anyone have an idea ?
Thank you.
I would suggest creating a map instead of list which you are converting to a set. This is because of the fact that sets will have the same keys and values [1]:
each.value — The map value corresponding to this instance. (If a set was provided, this is the same as each.key).
To convert the data to a map you could try:
Right_Groups = {for x in csvdecode(file("${path.module}/_RightGroups.csv")) : x.droits_groups => x.description}
This will create the following map:
> local.Right_Groups
{
"con-axw-rec01" = "rec01"
"con-inf-dev01" = "dev01"
}
Then, in the resource you would do:
resource "azuread_group" "Terra-Aad-Group-Right" {
for_each = local.Right_Groups
display_name = lower(each.key)
security_enabled = true
description = each.value
}
[1] https://www.terraform.io/language/meta-arguments/for_each#the-each-object
I have the following variable
variable "instance_types" {
default = {
instances : [
{
count = 1
name = "control-plane"
ami = "ami-xxxxx"
instance_type = "t2.large"
iam_instance_profile = "xxx-user"
subnet_id = "subnet-xxxxx"
},
{
count = 3
name = "worker"
ami = "ami-xxxxx"
instance_type = "t2.large"
iam_instance_profile = "xxx-user"
subnet_id = "subnet-xxxxx"
}
]
}
}
With the following instance declaration (that I'm attempting to iterate)
resource "aws_instance" "k8s-node" {
# Problem here : How to turn an array of 2 objects into 4 (1 control_plane, 3 workers)
for_each = {for x in var.instance_types.instances: x.count => x}
ami = lookup(each.value, "ami")
instance_type = lookup(each.value, "instance_type")
iam_instance_profile = lookup(each.value, "iam_instance_profile")
subnet_id = lookup(each.value, "subnet_id")
tags = {
Name = lookup(each.value, "name")
Type = each.key
}
}
Goal: Get the aws_instance to iterate 4 times (1 control_plane + 3 workers) and populate the values the index of instance_types.
Problem : Cannot iterate the over the object array correctly with desired result. In a typical programming language this would be achieved in a double for loop.
This can be solved easier with a data type of map(object)) for your input variable. The transformed data structure appears like:
variable "instance_types" {
...
default = {
"control-plane" = {
count = 1
ami = "ami-xxxxx"
instance_type = "t2.large"
iam_instance_profile = "xxx-user"
subnet_id = "subnet-xxxxx"
},
"worker" = {
count = 3
ami = "ami-xxxxx"
instance_type = "t2.large"
iam_instance_profile = "xxx-user"
subnet_id = "subnet-xxxxx"
}
}
}
Note the name key in the object is subsumed into the map key for efficiency and cleanliness.
If the resources are split between the control plane and worker nodes, then we are finished and can immediately leverage this variable's value in a for_each meta-argument. However, combining the resources now requires a data transformation:
locals {
instance_types = flatten([ # need this for final structure type
for instance_key, instance in var.instance_types : [ # iterate over variable input objects
for type_count in range(1, instance.count + 1) : { # sub-iterate over objects by "count" value specified; use range function and begin at 1 for human readability
new_key = "${instance_key} ${type_count}" # for resource uniqueness
type = instance_key # for easier tag value later
ami = instance.ami # this and below retained from variable inputs
instance_type = instance.instance_type
iam_instance_profile = instance.iam_instance_profile
subnet_id = instance.subnet_id
}
]
])
}
Now we can iterate within the resource with the for_each meta-argument, and utilize the for expression to reconstruct the input for suitable usage within the resource.
resource "aws_instance" "k8s-node" {
# local.instance_types is a list of objects, and we need a map of objects with unique resource keys
for_each = { for instance_type in local.instance_types : instance_type.new_key => instance_type }
ami = each.value.ami
instance_type = each.value.instance_type
iam_instance_profile = each.value.iam_instance_profile
subnet_id = each.value.subnet_id
tags = {
Name = each.key
Type = each.value.type
}
}
This will give you the behavior you desire, and you can modify it for style preferences or different uses as the need arises.
Note the lookup functions are removed since they are only useful when default values are specified as a third argument, and that is not possible in object types within variable declarations except as an experimental feature in 0.14.
The absolute namespace for these resources' exported resource attributes would be:
(module?.<declared_module_name>?.)<resource_type>.<resource_name>[<resource_key>].<attribute>
For example, given an intra-module resource, first worker node, and private ip address exported attribute:
aws_instance.k8s-node["worker 1"].private_ip
Note you can also access all resources' exported attributes by terminating the namespace at <resource_name> (retaining the map of all resources instead of accessing a singular resource value). Then you could also use a for expression in an output declaration to create a custom aggregate output for all of the similar resources and their identical exported attribute(s).
{ for node_key, node in aws_instance.k8s-node : node_key => node.private_ip }
Error: Unbalanced parentheses
on .terraform\modules\nics\main.tf line 19, in resource "azurerm_network_interface" "NIC1":
19: subnet_id = "${element(var.subnetwork-subnetid.*.id, (0,1))}"
output values of subnets:
output "subnetwork-subnetid" {
value = concat(azurerm_subnet.subnetwork.*.id, azurerm_subnet.subnetwork6.*.id)
}
nic.tf
resource "azurerm_network_interface" "NIC1" {
#count = "${length(var.subnetwork-subnetid)}"
#for_each= toset(var.subipv4)
count = "${length(var.subipv4)}"
name = "${lookup(element(var.subipv4, count.index), "name")}"
#name = var.nic-name
location = var.rg-location
resource_group_name = var.rg-name
enable_ip_forwarding = true
enable_accelerated_networking = true
ip_configuration {
name = "ipconfig"
subnet_id = "${element(var.subnetwork-subnetid.*.id, (0,1))}"
private_ip_address_allocation = "Dynamic"
#public_ip_address_id = azurerm_public_ip.pubip.id
#public_ip_address_id = azurerm_public_ip.pubip.*.id
primary = true
}
tags = {
name = "${lookup(element(var.subipv4, count.index), "name")}"
}
}```
Please someone help me in this issue.Thanks!
Second argument in element is index:
index finds the index for a particular element value.
Thus to get few elements from the list based on indices, you can do:
subnet_id = [ for idx in [0, 1]: element(var.subnetwork-subnetid.*.id, idx) ]
If you want a range of indies, you can use slice:
subnet_id = slice(var.subnetwork-subnetid.*.id, 0, 2)
In Terraform, I'm trying to create a DNS SRV record from created DNS A records. I would like to populate the records with the names from the aws_route53_record.etcd names, but running into errors when referencing the resource names.
Is there an easy way to achieve this?
# This resource works without errors
resource "aws_route53_record" "etcd" {
count = length(var.control_plane_private_ips)
zone_id = data.aws_route53_zone.test.zone_id
name = "etcd-${count.index}.${data.aws_route53_zone.test.name}"
type = "A"
ttl = 60
records = var.control_plane_private_ips
}
resource "aws_route53_record" "etcd_ssl_tcp" {
zone_id = data.aws_route53_zone.test.zone_id
name = "_etcd-server-ssl._tcp.${data.aws_route53_zone.test.name}"
type = "SRV"
ttl = 60
# code is producing an error here. Would like to add the names to the records
for_each = [for n in aws_route53_record.etcd : { name = n.name }]
records = [
"0 10 2380 ${each.value.name}.${data.aws_route53_zone.test.name}"
]
}
When running a terraform plan, I get the following error.
Error: Invalid for_each argument
on main.tf line 55, in resource "aws_route53_record" "etcd_ssl_tcp":
55: for_each = [for n in aws_route53_record.etcd : { name = n.name }]
The given "for_each" argument value is unsuitable: the "for_each" argument
must be a map, or set of strings, and you have provided a value of type tuple.
you use for_each and for in the same line. Both are describing loops and this makes it really hard to fallow. Try to split the line in 2 different lines and assign the for to a local variable. Splitting the for and for_each will help us check this.
I think the issue is [for n in aws_route53_record.etcd : { name = n.name }]
the starting bracket [for ... defines a list and the
{ name .. defines a map . So a list of maps. Perhaps to remove the { ?
Figured it out based on the feedback. Thanks for the help!
resource "aws_route53_record" "etcd_ssl_tcp" {
zone_id = data.aws_route53_zone.kubic.zone_id
name = "_etcd-server-ssl._tcp.${data.aws_route53_zone.test.name}"
type = "SRV"
ttl = 60
records = [
for n in aws_route53_record.etcd :
"0 10 2380 ${n.name}"
]
}
I have the block of code below that creates a bunch of subnets based on a list of names and a list of address_prefixes.
resource "azurerm_subnet" "subnet" {
count = "${length(var.subnet_names)}"
name = "${element(var.subnet_names, count.index)}"
resource_group_name = "${var.vnet_rg_name}"
virtual_network_name = "${data.azurerm_virtual_network.vnet.name}"
address_prefix = "${element(var.subnet_prefixes, count.index)}"
service_endpoints = ["Microsoft.Sql","Microsoft.Storage","Microsoft.AzureCosmosDB"]
network_security_group_id = "${data.azurerm_network_security_group.required_nsg.id}"
route_table_id = "${element(azurerm_route_table.routetable.*.id, count.index)}"
depends_on = ["azurerm_route_table.routetable"]
}
I am then trying to create some routes using a module but when I try to pass in values for variables using properties from a specific instance of the azurerm_subnet.subnet resource, it throws the error:
"module.insidedmzroutes.var.subnet_name: Resource 'azurerm_subnet.subnet' not found for variable 'azurerm_subnet.subnet.5.name'"
module "insidedmzroutes" {
source = "./modules/dmzroutes"
subnet_name = "${azurerm_subnet.subnet.5.name}"
vnet_rg = "${data.azurerm_resource_group.vnet_rg.name}"
route_table_name = "${azurerm_route_table.routetable.5.name}"
next_hop_ip = "${cidrhost(azurerm_subnet.subnet.5.address_prefix, 4)}"
subnet_names = ["${var.subnet_names}"]
subnet_prefixes = ["${var.subnet_prefixes}"]
}
Does this not work or do I have the reference constructed incorrectly?
Please have a look at the Terraform interpolation syntax documentation, look for interpolation syntax.
The following would work (as indicated by Adil B):
subnet_name = "${azurerm_subnet.subnet.*.name[5]}"
As with the splat syntax * you select all of the elements created using a count variable, which will then return a list, which you can select the correct element from [5].
However, why are you also passing along the entire list of subnets? Which subnets are these? It's not very clear from your code if these are the 5 subnets you created earlier or different ones. Are you creating an insidedmzroutes for every subnet? If so, I'd get rid of the subnet_name var and instead implement something like this in the resource inside the module:
count = "${length(var.subnet_names)}"
subnet_name = "${element(var.subnet_names, count.index)}"