terraform icon indicating copy to clipboard operation
terraform copied to clipboard

Using count and for_each together

Open paololazzari opened this issue 3 years ago • 4 comments

When using both together:

The "count" and "for_each" meta-arguments are mutually-exclusive, only one should be used to be explicit about the number of resources to be created.

What's the reason for this limitation? This seems like such a weird thing.

My use case is identical to this one https://github.com/hashicorp/terraform/issues/30030

paololazzari avatar Aug 03 '22 13:08 paololazzari

Hi @paololazzari,

Objects in terraform can be expanded into a number of instances, keyed by either a numeric index, or a string key; there is no way to use both as an index into the same structure. If there are multiple sources of key values for a resource, they can be combined into a single for_each expression using a single level of expansion.

We use GitHub issues for tracking bugs and enhancements, rather than for questions. While we can sometimes help with certain simple problems here, it's better to use the community forum where there are more people ready to help.

Thanks!

jbardin avatar Aug 03 '22 13:08 jbardin

Mine wasn't a question, it was an enhancement request which is why I opened it with with the "enhancement" tag. Can you reopen it please?

paololazzari avatar Aug 03 '22 13:08 paololazzari

Hi @paololazzari,

Could you elaborate a bit on the points from the issue template, like what the intended use case is, and what the proposal would look like? The literal interpretation of using count+for_each is not possible in terraform as it exists now, which would be to structure all resources as nested maps in a list (or vise versa). While that could allow for using both expansion modes, it would also require rewriting how terraform evaluates all resources and configuration, and make configuration generally more complex. This means it's not likely something that will be implemented given other possible solutions.

If multiple nested levels of configuration are needed, forcing that to be only 2 levels deep and consisting of an ordered level and a map level is somewhat arbitrary and limiting. More flexibility can be gained by generating a flat data structure containing all of the desired instances without such limitations.

jbardin avatar Aug 03 '22 22:08 jbardin

Hey @paololazzari, I think I know what you are trying to achieve, correct me if I'm wrong. You want to create a resource with the for_each meta-argument, but on a condition. That can be achieved by adding the conditional to for_each and without count. See:

locals {
  workspace    = terraform.workspace
  for_each_set = toset(["1", "2", "3"])
}

resource "random_string" "conditional_for_each_resource" {
  for_each = local.workspace == "staging" ? local.for_each_set : []
  length   = 10
}

workspace default

$ terraform workspace list 
* default
  staging

$ terraform apply 

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

workspace staging

$ terraform workspace select staging 
Switched to workspace "staging".
$ terraform apply 

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

[…]

random_string.conditional_for_each_resource["3"]: Creating...
random_string.conditional_for_each_resource["1"]: Creating...
random_string.conditional_for_each_resource["2"]: Creating...
random_string.conditional_for_each_resource["1"]: Creation complete after 0s [id=TYl*-%z5#*]
random_string.conditional_for_each_resource["3"]: Creation complete after 0s [id=BU3jTP6RQ]]
random_string.conditional_for_each_resource["2"]: Creation complete after 0s [id=83RzgTf[t5]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

bastiandg avatar Aug 10 '22 07:08 bastiandg

In addition to @bastiandg's suggestion, there's a more general form of this which allows more detailed conditions that conditionally exclude only a subset of the elements in input map or set:

resource "example" "example" {
  for_each = {
    for k, v in var.something : k => v
    if arbitrary_condition_here
  }
}

The arbitrary_condition_here expression can, but does not need to, refer to k and v to make decisions on a per-element basis.

In the simple case where you just want to disable something entirely based on a global value, you can write a condition that doesn't refer to the element key or element value, like this:

variable "enable_thing" {
  type = bool
}

resource "example" "example" {
  for_each = {
    for k, v in var.something : k => v
    if var.enable_thing
  }
}

In any for expression where the if clause refers to neither the key or the value the expression will either keep all of the elements or filter all of the elements out, which in the latter case creates the same effect as setting count = 0.

apparentlymart avatar Aug 31 '22 19:08 apparentlymart

Hello,

Since there has been no further updates about original topic, I'm going to close the issue. If you have more questions, feel free to start a discussion in the community forum.

Thanks!

jbardin avatar Sep 12 '22 12:09 jbardin

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

github-actions[bot] avatar Oct 13 '22 02:10 github-actions[bot]