bhamichi
bhamichi

Reputation: 441

combine "count" and "for_each" is not possible

This is my code source

resource "aws_s3_bucket_object" "object" {
  count               = var.s3_create[1] ? 1 : 0 
  depends_on          = [aws_s3_bucket.bucket_backup]
  for_each            = local.buckets_and_folders 
    bucket            = each.value.bucket_backup
    key               = format("%s/", each.value.folder)
  force_destroy       = true
} 

In other words, I'm traying to create object aws_s3_bucket_object depends on variable s3_create ... create if true else not create.

Issue: I am not able to use the combination of both the below syntax in creating the terraform resource and I'm geeting :

Error: Invalid combination of "count" and "for_each"
│
│   on ..\s3\resources.tf line 51, in resource "aws_s3_bucket_object" "object":
│   51:   for_each            = local.buckets_and_folders
│
│ 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.

Upvotes: 13

Views: 21764

Answers (3)

Andrew Haigh
Andrew Haigh

Reputation: 79

It is possible to combine "count" and "for_each" in a terraform resource block

But you have to be "dynamic" about it

Here's an example of what I have in place (and working)

resource "azurerm_network_security_group" "nsg-001" {
count               = var.nsg-deploy.deploy ? 1 : 0

dynamic "security_rule" {
    for_each = local.nsg_001_ruleset_001
    content {}
}

Cheers

-=A=-

.

Upvotes: 0

Martin Atkins
Martin Atkins

Reputation: 74719

The rule for for_each is that you should assign it a map or set that has the same number of elements as the number of instances you want to declare.

Thinking about your goal from that standpoint, the solution will involve making your map be empty in situations where you want to declare no instances. The typical way to filter elements from a collection is to write [a for expression] with an if clause.

From what you shared in your example I can't tell if local.buckets_and_folders is a map or a set of strings, so I'll show examples for both...

If it's a map:

  for_each = tomap({
    for k, v in local.buckets_and_folders : k => v
    if var.s3_create[1]
  })

If it's a set:

  for_each = toset([
    for v in local.buckets_and_folders : v
    if var.s3_create[1]
  ])

In both cases the if clause means that the result will be an empty collection if var.s3_create[1] is false.

There may be other ways to design your module so that the input and the derived expressions can be simpler, but working within the relatively limited examples you shared the above is the most direct answer to your question. If you'd like to talk about possible ways to simplify this, you could ask a new question on Stack Overflow and include a fuller example of your current code and the underlying requirements this module is intended to meet.

Upvotes: 3

Dan Monego
Dan Monego

Reputation: 10117

Both count and for_each apply to the whole block. Indenting lines underneath a for_each doesn't impact anything but human readability.

Try using the ternary operator with a for_each instead of a count. If the value is false, return an empty set.

resource "aws_s3_bucket_object" "object" {
  for_each       = var.s3_create[1] ? tomap({local.buckets_and_folders}) : {}
  bucket         = each.value.bucket_backup
  key            = format("%s/", each.value.folder)
  depends_on     = [aws_s3_bucket.bucket_backup]
  force_destroy  = true
} 

Upvotes: 24

Related Questions