Conditionally create resources when a for_each loop is involved

A technique for the conditional creation of resources is to have a line immediately after the one with name of the resource like:

count = var.create_resource ? 1 : 0

However, for a resource that contains a for_each loop this does not work.

Is there a way of coding a resource that includes a for_each loop in such away that the resources only get created if a boolean variable is set to true ?.

Hi @chrisadkin,

The key thing about for_each is that it declares one resource instance for each element in the map (or set) assigned to it.

Building on that, the key to your question is to make sure that the for_each map has zero elements in the cases where you want to create nothing. If you have a sort of “all or nothing” situation – where you’ll disable all of the elements together in the “off” case – then a relatively concise way to write it is to write a for expression with an if clause that always evaluates to true only if your condition is enabled:

  for_each = { for k, v in var.some_map : k => v if var.enabled }

Typically when writing an expression like this the if clause would contain a reference to k and/or v, but since this is an “all or nothing” situation the result depends only on some external flag and doesn’t vary for each element.

If you do want to vary the behavior by element then you can certainly do that too. For example:

  for_each = {
    for k, v in var.some_map : k => v
    if contains(var.enabled_keys, k)
  }

(the above is assuming that var.enabled_keys is a set(string) value containing the keys of the var.some_map elements that ought to be enabled, just as a contrived example of an item-specific condition.)

1 Like

You can’t have count and for_each on a data source, resource or module.
A trick to filter a map to remove items based on your condition.

variable "mymap" {
    type = map(object({
        attribute = string
        condition = bool
    }))

    default = {
        key1 = {
            attribute = "value"
            condition = true
        }
        key2 = {
            attribute = "value"
            condition = false
        }
        key3 = {
            attribute = "value"
            condition = true
        }
    }
}

resource "null_resource" "test" {
  for_each = { for k in compact([for k, v in var.mymap: v.condition ? k : ""]): k => var.mymap[k] }
}

Hi @apparentlymart,
It is possible to conditionally create a resource with resource just in place and leaving resource address unchanged ?

e.g.

(starting code)

module "ec2one" {
  source = "..."
  ...
}

will produce resource address:

module.ec2one.<attributes>

(refactoring with count)

module "ec2one" {
  source = "..."
  count = var.ec2one_created ? 1 : 0  
  ...
}

will produce resource address:

module.ec2one[0].<attributes>

(refactoring with for_each)

module "ec2one" {
  source = "..."
  for_each = { for k in ["created"] : k => k if var.ec2one_created }
  ...
}

will produce resource address:

module.ec2one[0].created.<attributes>

(refactoring without address changes)

module "ec2one" {
  source = "..."
  <some conditional method not changing resource address>
  ...
}

will produce resource address as original:

module.ec2one.<attributes>

In reality there is no such thing as “conditional create”, just the ability to choose the number of resources to create (we just happen to only choose zero or one). Therefore there has to be a way to handle the count/for_each creating more than 1 resource - hence the change to either [1] or [“something”].

So in short, no adding count of for_each would adjust the resource name, but you could easily update any existing resource using terraform state mv

I tried terrform import with some trouble.

Terraform import aws_instance force replacement for network_interface

I added count to an aws_instance (e.g. bastionaws1) to allow conditional creation, so I need to terraform import new bastionaws1[0] linked resources and remove old ones (basrionaws1). I correctly imported aws_instance, the instance has attached ENI so I also imported resource network_interface. On terraform plan I get that instance will be replaced becouse network_interface force replacement:

 # module.bastionaws1[0].aws_instance.this must be replaced
-/+ resource "aws_instance" "this" {
   ...
     + network_interface { # forces replacement
         + delete_on_termination = false
         + device_index          = 0
         + network_interface_id  = "eni-..."
       }

Not tried terraform state mv yet but probably I’ll get same result.

Using terraform state mv worked

thanks @stuart-c

It would be very useful if terraform could handle single element list as a single normal resource. This could simplify later adoption of conditional resources creation avoiding complex, risky and boring refactoring.

FYI, before finding this discussion I solved the problem this way:

for_each = var.create-bastions ? toset(local.users) : []

Worked For Me ™