Loop over resource instances in templatefile

I have a bunch of resource instances (e.g. digitalocean_droplet) and I would like to generate an ansible inventory file from it. What’s the best way of doing this?

I would have expected that one could pass “all droplets instances” to the template context and then loop over it:

resource "local_file" "ansible_inventory" {
  content = templatefile(
	"templates/ansible_inventory.tmpl",
	{
	  names = digitalocean_droplet.*,
	}
	)
  filename = "../ansible/hosts"
}

But then I get:

“A reference to a resource type must be followed by at least one attribute access, specifying the resource name.”

I’m fairly certain digitalocean_droplet.* is an invalid reference; you’re not actually accessing any attribute. You haven’t shared your template, so I can’t be certain, but I assume what you actually want to set as the value for names is:

digitalocean_droplet.foo[*].name
1 Like

That won’t work either.

There is no way to do this, as Terraform doesn’t provide support for introspecting all resources of a given type within its expression language.

You would need to load and process the Terraform state after the run is complete, using a general purpose programming language.

Alternatively, if you can restructure your configuration so that all of your droplets are defined as elements of a for_each, on a small fixed number of resource blocks that you reference manually, you could access digitalocean_droplet.name_of_resource[*].

1 Like

Hi @winpat,

In an expression like digitalocean_droplet.foo, these two parts are indivisible because they are both necessary to uniquely identify a resource block. This error message is reporting that this expression isn’t sufficient for Terraform to determine which resource block it is referring to.

To make this work you’ll need to build a single value containing all of the data you want to pass into the template. There are two main ways to do that:

  • If all of your “droplets” have similar configuration then you could declare them all together in a single resource "digitalocean_droplet" "..." block using the for_each meta-argument. That then makes a reference to the resource in expressions return a map of objects instead of a single object, and you can pass that map into your template as a value.

  • If they all have very different configurations such that it would be confusing to try to declare them all systematically using a single resource configuration, you can instead construct a collection yourself containing an arbitrary set of resource objects. For example:

    locals {
      droplets_for_ansible = [
        digitalocean_droplet.foo,
        digitalocean_droplet.bar,
        digitalocean_droplet.baz,
      ]
    }
    

    You can then pass local.droplets_for_ansible to the template. This approach can work because Terraform can clearly see that local.droplets_for_ansible depends on digitalocean_droplet.foo, digitalocean_droplet.bar, and digitalocean_droplet.baz, and thus it’s clear that the call to templatefile depends (indirectly) on all three.

1 Like

Thanks for the help @sudoforge , @maxb and @apparentlymart . I think I will generate the inventory from the terraform state. This seems to be fairly simple and does exactly what I need.

Cool @winpat! I’m glad this helped you find a path forward.

Note that the state format Terraform stores in the backend is internal to Terraform and subject to change in future, so for external integration you should use the public-facing format which you can obtain using the following command after the apply completes:

terraform show -json

This particular command (without a plan file argument) returns the most recent state snapshot in the format described under State Representation.