Constructing resource names within Terraform

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