Rome_Leader
Rome_Leader

Reputation: 2700

Needing multiple counts on a Terraform resource?

I have the following Terraform resource which is intended to create a separate encrypted EFS volume per warehouse, and then create mount targets in two subnets for each:

resource "aws_efs_file_system" "efs-data-share" {
  count      = "${length(var.warehouses)}"
  encrypted  = "${var.encrypted}"
  kms_key_id = "${element(data.aws_kms_key.encryption_key.*.arn,count.index)}"
  performance_mode = "${var.performance_mode}"

  tags {
    Name        = "${element(split(".",var.warehouses[count.index]),0)}-${var.name_suffix}"
    Warehouse   = "${element(split(".",var.warehouses[count.index]),0)}"
    Environment = "${var.environment}"
    Purpose  = "${var.purpose}"
  }
}

resource "aws_efs_mount_target" "mounts" {
  count           = "${length(var.subnets)}"
  file_system_id  = "${aws_efs_file_system.efs-data-share.*.id}"
  subnet_id       = "${element(var.subnets, count.index)}"
  security_groups = ["${var.efs_security_groups}"]
}

data "aws_kms_key" "encryption_key" {
  count = "${length(var.warehouses)}"
  key_id = "alias/${element(split(".",var.warehouses[count.index]),0)}-${var.key_alias_suffix}"
}

The EFS themselves launch fine, but the mounts fail, because the file_system_id must be a single resource, not a list.

    * module.prod_multi_efs.aws_efs_mount_target.mounts[1]: file_system_id must be a single value, not a list
    * module.prod_multi_efs.aws_efs_mount_target.mounts[0]: file_system_id must be a single value, not a list

I'm not sure what my recourse is here in terms of Terraform syntax. I need two mounts in each case, so my count is on the length of the subnets variable, for which I'm passing in subnet-123456 and subnet-567890. How can I get the system to make both mounts for each EFS made (seven in total by length of warehouses list) without being able to use a second count within the aws_efs_mount_target?

Here is how I am calling the module:

module "prod_multi_efs" {
  source              = "../../../../Modules/Datastore/MultiEFS"
  efs_security_groups = ["${aws_security_group.prod-efs-group.id}"]
  environment         = "Prod"
  name_suffix         = "${module.static_variables.prod_efs_name}"
  key_alias_suffix    = "prod-efs-${module.static_variables.account_id}-us-east-1"
  subnets             = ["${module.prod_private_subnet_1.subnet_id}", "${module.prod_private_subnet_2.subnet_id}"]
  warehouses          = "${module.static_variables.warehouses}" #The list of seven warehouses
}

Here is my plan with the attempt of adding [count.index] as per the answer below - it does two mounts, but only to the first 2 of the 7 FS IDs:

  + module.stg_multi_efs.aws_efs_mount_target.mounts[0]
      id:                                      <computed>
      dns_name:                                <computed>
      file_system_arn:                         <computed>
      file_system_id:                          "fs-123456"
      ip_address:                              <computed>
      network_interface_id:                    <computed>
      security_groups.#:                       "1"
      security_groups.4286231943:              "sg-xxxxxxxxxx"
      subnet_id:                               "subnet-aaaaaaaa"

  + module.stg_multi_efs.aws_efs_mount_target.mounts[1]
      id:                                      <computed>
      dns_name:                                <computed>
      file_system_arn:                         <computed>
      file_system_id:                          "fs-567890"
      ip_address:                              <computed>
      network_interface_id:                    <computed>
      security_groups.#:                       "1"
      security_groups.4286231943:              "sg-xxxxxxxxxxx"
      subnet_id:                               "subnet-bbbbbbbbbb"

Upvotes: 5

Views: 11371

Answers (2)

srinivas majeti
srinivas majeti

Reputation: 1

If I do this , I get the following error The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the for_each depends on."

Upvotes: 0

Martin Atkins
Martin Atkins

Reputation: 74219

Terraform 0.12 introduced a new function setproduct for situations like this where you need a set of objects representing every combination of elements from two or more collections:

locals {
  data_share_subnets = [
    for pair in setproduct(aws_efs_file_system.efs-data-share, var.subnets) : {
      file_system_id = pair[0].id
      subnet_id      = pair[1]
    }
  ]
}

The above declares local.data_share_subnets as a data structure something like this:

[
  {"file_system_id": "fs-123456", "subnet_id": "subnet-aaaaaaaa"},
  {"file_system_id": "fs-123456", "subnet_id": "subnet-bbbbbbbb"}
  {"file_system_id": "fs-567890", "subnet_id": "subnet-aaaaaaaa"},
  {"file_system_id": "fs-567890", "subnet_id": "subnet-bbbbbbbb"}
]

You can then use that with the new for_each feature introduced in 0.12.6 to declare one aws_efs_mount_target instance per entry in that collection:

resource "aws_efs_mount_target" "mounts" {
  for_each = { for o in local.data_share_subnets : "${o.file_system_id}:${o.subnet_id}" => o }

  file_system_id  = each.value.file_system_id
  subnet_id       = each.value.subnet_id
  security_groups = var.efs_security_groups
}

The for expression in the for_each argument transforms the list shown above into a map whose keys are like "fs-123456:subnet-aaaaaaaa". Terraform will then track these instances using addresses like this:

  • aws_efs_mount_target.mounts["fs-123456:subnet-aaaaaaaa"]
  • aws_efs_mount_target.mounts["fs-123456:subnet-bbbbbbbb"]

...which means you can safely add and remove individual filesystems and subnets without disturbing any other unrelated mount target instances. (that would not be true using count, because the instances would be identified by their positions in the list.)

There is no direct equivalent of the above in Terraform 0.11 or earlier.

Upvotes: 12

Related Questions