Hi @igor-nikiforov,
Before getting into the specific syntax, I want to note that the way you’re using template_file
here is redundant: the expression you assigned to template
is itself a string template expression that’ll already be rendered by the time the value gets passed into the template_file
data source, and so there’s nothing left for the data source to render at that point. template_file
is a deprecated data source which remains only to support folks still using Terraform v0.11 or earlier, which didn’t yet have the full template syntax we have in modern Terraform.
Given that, I’d start by replacing your data "template_file"
block with a local value declaration, like this:
locals {
metadata = [
for i in range(var.vm_count) : <<EOF
interface 1 = ${var.ipv4_addresses[i].eth0}
interface 2 = ${var.ipv4_addresses[i].eth1}
EOF
]
}
In the above I used a for
expression with the range
function to get the same effect as the count = var.vm_count
in your example, and so the result of this would be a list of strings where each string is the result of rendering the template for one of your VMs.
If I’ve understood your goal correctly, I think the above should work allowing you to use local.metadata
to access the rendered templates.
However, having multiple separate variables that all together have to agree on how many elements there are and correlate by index tends to be quite hard to maintain from the caller’s perspective, because they need to be careful to keep the indexes correlated, so I’d suggest thinking about a different organization where everything about your VMs is together in a single variable. You only showed the count and the IP addresses in this example and so I’ll focus on those for the following example, but hopefully you can see how this could extend to capture other important attributes for each virtual machine, if any:
variable "vms" {
type = list(object({
interfaces = object({
eth0 = string
eth1 = string
})
}))
}
locals {
vm_metadata = [
for vm in var.vms : <<EOF
interface 1 = {vm.interfaces.eth0}
interface 2 = {vm.interfaces.eth1}
EOF
]
}
# You didn't mention which cloud provider you're
# using, so I've just used AWS here as an
# example.
resource "aws_instance" "example" {
# The length of var.vms implies the desired number of VMs
count = length(var.vms)
# ...
# Select the corresponding element from local.vm_metadata
# to use as the user_data.
user_data = local.vm_metadata[count.index]
}
An advantage of this structure is that a user of your module can just add new elements to the vms
variable to both increase the number of VMs and define their network interface settings at the same time, and so there isn’t any risk of forgetting to update one of them or declaring the elements of one of the lists in the wrong order.
(I used count
here because that’s how you structured your example, but note that this is appropriate only for situations where all of your VMs are interchangeable in the sense that if you needed to scale down the number then it wouldn’t matter which one is to be destroyed. If the instances are not interchangeable – that is, if you need to control the lifecycle of each one specifically – then it’d be more appropriate to use for_each
, but that’s getting quite beyond the scope of what you asked so I won’t get into that here.)