Pass For_each output to another Module

I’d like to know if what I am trying to do is poaaible.

I have 2 modules that I call. The first creates multiple EC2’s (using for_each) and outputs the EC2_id.

I then want to pass that output to another module and do a for_each to get the instance ID for volume attachment.

EC2 module called
For_each used
Output multiple EC2 id’s
EBS module called (ec2_id output input as a var)
loop through ec2_id for EBS attachment

The above is a simplified version. Is it possible to pass the output of a for_each to another module?

Hi @jprouten,

A resource with for_each set appears in expressions as a map of objects whose keys are the same as the keys in the for_each collection. Aside from being of an object type derived automatically from the resource type schema, these objects are just normal values that you can use in Terraform expressions in all of the same ways that you could use objects (and maps of objects) you created yourself.

However, since modules typically aim to encapsulate details of the resources they declare, it’s more common to derive a simpler map from the resource’s map which only includes the details that the external caller needs. In your case, it sounds like that’s just the EC2 instance ids.

To achieve that, your EC2 module might include an output value declared like this:

output "instance_ids" {
  value = tomap({
    for k, inst in aws_instance.example : k => inst.id
  })
}

The above uses a for expression to project the map of objects from aws_instance.example into a simpler map of strings, where the keys are the same as the instance keys but the values are just the instance ids, and not any of the other resource attributes.

You can then match this in your EBS module by declaring a variable of the same type to receive the value:

variable "ec2_instance_ids" {
  type = map(string)
}

resource "aws_volume_attachment" "example" {
  for_each = var.ec2_instance_ids

  # ...
}

This can work because var.ec2_instance_ids is a map and so it’s compatible with the requirements of for_each. The volume attachments will have the same instance keys as the EC2 instances they are attaching to, so you’ll be able to more easily see the correlations between them in the plan output.

Then in the root module you can connect those two together, something like this:

module "ec2" {
  source = "modules/ec2"

  # ...
}

module "ebs" {
  source = "modules/ebs"

  ec2_instance_ids = module.ec2.instance_ids
  # ...
}

Perfect. That worked, thanks!

In the for_each I need to pass in both the instance ID and the Instance Availability zone.
I can see it’s possible to construct this in my outputs, but I can’t find a good example or explanation on how to do it.

How would I create an output that had both the Instance ID and the AvailabilityZone?

When you need to return more than one value for the same object then that’s a good use for object types. The principle will be similar but we’ll change the output value and the input variable to use a map of an object type rather than a map of strings:

output "instances" {
  value = tomap({
    for k, inst in aws_instance.example : k => {
      id                = inst.id
      availability_zone = inst.availability_zone
      private_ip        = inst.private_ip
    }
  })
}
variable "ec2_instance_ids" {
  type = map(object({
    id                = string
    availability_zone = string
  }))
}

resource "aws_volume_attachment" "example" {
  for_each = var.ec2_instance_ids

  instance_id = each.value.id
  # ...
}

I also included private_ip on the output value in order to illustrate something about object type constraints: the variable’s type constraint only includes id and availability_zone, but that’s okay because Terraform will accept any object type that has at least the specified attributes, and so it’ll just ignore that extra private_ip attribute during type conversion.

That’s not very important for this limited case, but it can be useful if you have a number of other modules that need information about instances but that each need different information: you can make the module return all of the information all of the modules need but have each module only specify on the specific attributes it needs.

Worked a treat. Thanks!