Search code examples
foreachterraform

Terraform failing with Invalid for_each argument / the "for_each" argument must be a map, or set of strings


When running terraform plan I get the following error: The given "for_each" argument value is unsuitable: the "for_each" argument must be a map, or set of strings, and you have provided a value of type tuple.

I'm trying to create buckets, but either allow the user to provide a kms key or looking it up for them.

│ Error: Invalid for_each argument
│ 
│   on ../ec2-pattern/main.tf line 39, in module "s3_bucket":
│  329:   for_each = local.s3_bucket_list
│     ├────────────────
│     │ local.s3_bucket_list is tuple with 1 element
│ 
│ The given "for_each" argument value is unsuitable: the "for_each" argument 
| must be a map, or set of strings, and you have provided a value of type tuple.
dev.tfvars

s3_buckets = {
  bucket1 : {

    policy_bucket_modify_arns_csv = "s3-policy-arns/s3-policy-modify-arns.csv"
    policy_bucket_read_arns_csv   = "s3-policy-arns/s3-policy-read-arns.csv"
    bucket_suffix                 = "test"
    contains_pci_info             = false
  }
}

main.tf

locals {
  s3_bucket_list = flatten([
    for k, v in var.s3_buckets :
    merge(v, { kms_master_key_id = lookup(v, "kms_master_key_id", null), kms_master_key_id = module.datasource-module.data.s3_kms.arn_alias })

  ])
}

module "s3_bucket" {

  for_each = local.s3_bucket_list

  create_bucket                 = each.value.create_bucket
  policy_bucket_read_arns_csv   = each.value.policy_bucket_read_arns_csv
  policy_bucket_modify_arns_csv = each.value.policy_bucket_modify_arns_csv
  kms_master_key_id             = each.value.kms_master_key_id
}

variable "s3_buckets" {
  description = "A map of all buckets"
  type        = any
  default     = {}
}

Solution

  • The error seems clear:

    given "for_each" argument value is unsuitable ... must be a map, or set of strings, and you have provided a value of type tuple...

    To troubleshoot something like that you have to output your s3_bucket_list so you can confirm is the correct type, in your case your output is a list, you are doing flatten([ that only works on a list:
    https://developer.hashicorp.com/terraform/language/functions/flatten

    Here is how I would change your code:

    variable "s3_buckets" {
      default = {
        bucket1 : {
          bucket_suffix     = "test"
          contains_pci_info = false
        }
        bucket2 : {
          bucket_suffix     = "abc"
          contains_pci_info = true
          kms_master_key_id = "123"
        }
      }
    }
    
    locals {
      s3_bucket_list = {
        for k, v in var.s3_buckets :
        k => merge(v, { kms_master_key_id = lookup(v, "kms_master_key_id", "foo") })
      }
    }
    
    output "s3_bucket_list" {
      value = local.s3_bucket_list
    }
    

    If you do a terraform plan on that we get

    Changes to Outputs:
      + s3_bucket_list = {
          + bucket1 = {
              + bucket_suffix     = "test"
              + contains_pci_info = false
              + kms_master_key_id = "foo"
            }
          + bucket2 = {
              + bucket_suffix     = "abc"
              + contains_pci_info = true
              + kms_master_key_id = "123"
            }
        }
    

    that is a proper map that we can use in the for_each ... things I've changed:

    • added one more element to the variable s3_buckets to show better output
    • remove the flatten, your structure is flat, no clue why you needed that
    • in the lookup I changed the null for foo you can put any default you want there, like what you had module.datasource-module.data.s3_kms.arn_alias
    • in the return of the loop now we do k => ... that creates the map structure and we are keeping the original key from s3_buckets

    Last thought now with this change maybe you should not name it s3_bucket_list since that is not a list. I hope that is all clear enough.