Search code examples
terraformdatadogterraform-modulesterraform-provider-datadog

Is there a way to give a list of all x resources to a module in terraform?


We use terraform for datadog, and we have set up folders/modules per team. For Datadog synthetics tests, we have set up one datadog_synthetics_private_location for each environment/region combo (dev/pre-prod/prod-1/prod-2) in the terraform project root.

Each team can set up their own synthetics tests for their product domain, and the module should use those predefined synthetics location IDs instead of hardcoding them as a string.

What I would like to do is give all 3 (potentially n in the future) instances of datadog_synthetics_private_location that exist in the project to each module as a list, without creating n variables in each module.

I know about the * syntax in terraform, but AFAIK it works only on resources with count, which we don't have in our case because we want to be able to identify which resource out of n we are referencing:

# works (if my_location has a count):
location_ids = datadog_synthetics_private_location.my_location.*.id

# what I want to do:

# hand over to module:
locations = datadog_synthetics_private_location.*

# use:
locations = [
  var.locations.my_dev_location.id
]

Is this possible? Is it a bad design? Does it require an ugly hack? Is using the datadog_synthetics_locations data source a better approach? If yes, how? It looks like it retrieves all of them in a map, which I would then have to filter to retrieve a specific one.


Solution

  • The identifier datadog_synthetics_private_location alone doesn't represent a value, so you can't use it in expressions without it being followed by at least a resource name. Terraform uses these references to automatically infer the dependency graph, so the references must always be written statically.

    If you do need a collection value containing multiple resource objects then you can construct it yourself as a local value:

    locals {
      locations = {
        a = datadog_synthetics_private_location.a
        b = datadog_synthetics_private_location.b
        c = datadog_synthetics_private_location.c
      }
    }
    

    This approach can work because Terraform can see that local.locations depends on all three of these resources, and so any place where you refer to local.locations Terraform can infer that you are indirectly depending on all three resources. Once you've written out this data structure in one place you can use it as a normal value elsewhere, including passing it as a whole into a module or using it with any Terraform language operator or function that would accept a value of this type.


    The automatic type of local.locations as defined above would be an object type with a, b, and c attributes whose own types are the effective object type of datadog_synthetics_private_location.

    But when passing structures like this to child modules it's common and idiomatic to rely on Terraform's structural type system to describe only the subset of the data structure that the module actually relies on, rather than trying to describe that type exactly as Terraform would infer it.

    That involves referring to the documentation for datadog_synthetics_private_location to decide which of the attributes are the minimum your module needs to do its work, and then declaring a variable whose type is based on that.

    For example, if you conclude that your module only needs id and name then you could declare that like this:

    variable "locations" {
      type = map(object({
        id   = string
        name = string
      }))
    }
    

    If you pass locations = local.locations into the child module then Terraform will automatically convert the original inferred object type into a map of objects with the attributes you specify, ignoring any other attributes because the module does not declare that it needs them.

    Inside the module then var.locations will appear as a map of this object type, and you can rely on that always being the case or else Terraform would raise a type error in the calling module block.