I have a set of CNAME records to be maintained via the Cloudflare provider.
The set contains a subdomain and associated endpoint.
If I use count
I end up with names like cname_record[0]
, cname_record[1]
, etc.
All looking good so far.
As long as we only ever add new CNAMEs to the end of the list, all is good. As soon as we delete one all hell breaks loose as the records after that are deleted and recreated and then we have DNS propagation issues, etc.
What I want to do is have:
resource "cloudflare_record" "cname_record_${var.cnames[count.index].subdomain}" {
But Terraform does not allow variable resource names.
One solution we have had to build is an external rendering system that takes a template and builds the .tf
file (it has a BIG header that says that this is a generated file, but not ideal) so that each resource is uniquely named and is not dependent upon the position in the list. So, if it is the api
subdomain, the resource name is cname_record_api
, regardless of it is the first record or the last record in the list.
I’ve tried to get my head around the template feature offered by Terraform, but this seems to ONLY be useful for resource attribute values, not resources themselves.
Am I missing a way to do this?
Hi @rquadling,
What you are trying to do here is what resource for_each
is for. It’s a feature in the same spirit as count
(and mutually exclusive with it), where rather than creating a number of instances identified by consecutive integers it instead creates a number of instances corresponding to elements in a given map, using the map keys as the identifiers.
You didn’t show a full example of what var.cnames
looks like but I’m inferring from your example that it at least has an attribute subdomain
which you’d like to have as your key. In which case, the initial declaration with for_each
could look like this:
resource "cloudflare_record" "cname" {
for_each = { for cn in var.cnames : cn.subdomain => cn }
# ...
}
That for_each
expression is a for
expression to transform your list of objects into a map of objects, using the subdomain
attribute as the map key.
In the rest of the configuration you can use each.value
to refer to a particular attribute of the current element of var.cnames
. For example, you could use each.value.subdomain
to access the subdomain attribute.
This will result in resources with addresses like this:
cloudflare_record.cname["foo"]
cloudflare_record.cname["bar"]
…where “foo” and “bar” are example values for subdomain
in your var.cnames
. Adding and removing items from that list should then have the result you were looking for, as long as you ensure that the subdomain
values are unique across the entire list.
You might find it interesting to refer to the modules in the terraformdns
organization, where I was experimenting with modules to abstract over the differences between different DNS providers. I didn’t try the cloudflare
provider yet (I’ve been busy with other things recently so not had time to continue work on that) but maybe some of the others will show some interesting patterns to further generalize your DNS module.
(I originally wrote those prior to the introduction of the resource for_each
feature, so not all of them are updated to use it: some of them are still using count
, with the same limitation you encountered here. The patterns for constructing the collections of objects to deal with the modelling differences between the different DNS hosting vendors may still be interesting though, if adapted to use for_each
instead of count
.)
1 Like
This looks EXTREMELY useful. If I can use this, I can get rid of a LOT of some dumb templating.
Thank you very much for your feedback.
Thank you for this! It has worked perfectly for the first case I’ve needed this for.
I have a follow-up question : Accessing resources indirectly