skip to Main Content

I have a terraform tfvars file with a map of values that looks like this:

name_map = [
  {
    name         = "devbox"
    device_names = ["/dev/xvdg", "/dev/xvdh"]
    volume_size = ["900", "200"]
    group        = "hosts"
    instance_type = "m5a.2xlarge"
  },
  {
    name         = "devbox2"
    device_names = ["/dev/xvdg", "/dev/xvdh"]
    volume_size = ["300", "200"]
    group        = "hosts"
    instance_type = "m5a.2xlarge"
  }
]

] My tf file looks like this:

resource "aws_instance" "node" {
  count         = length(var.name_map)

  dynamic "ebs_block_device" {
    for_each = [for device in var.name_map.*.device_names[count.index] : {
      device_name = device,
      volume_size = var.name_map.*.volume_size[count.index]
    }]
    content {
      device_name           = ebs_block_device.value.device_name
      volume_type           = "gp2"
      volume_size           = ebs_block_device.value.volume_size
      delete_on_termination = true
    }
  }

So basically for the "devbox" instance I’d like "/dev/xvdg" to be 900 gbs, and "/dev/xvdh" to be 200 gbs. I’d like The current setup works to iterate through the device names of each mapping and get a single volume size but I’m trying to expand it to include different volume sizes for each device.

How would I do this?

I’ve tried a nested for_each statement but I keep getting errors. Would a flatten structure be the solution here? I’d love to see an example of what this would look like.

2

Answers


  1. I think what you want to do is the following:

    resource "aws_instance" "node" {
      count = length(var.name_map)
    
      instance_type = var.name_map[count.index].instance_type
      ami = "..." # Fill in a valid AMI here
    
      dynamic "ebs_block_device" {
        for_each = [for i, device in var.name_map[count.index].device_names : {
          device_name = device,
          volume_size = var.name_map[count.index].volume_size[i]
        }]
        content {
          device_name           = ebs_block_device.value.device_name
          volume_type           = "gp2"
          volume_size           = ebs_block_device.value.volume_size
          delete_on_termination = true
        }
      }
    }
    

    While this works, I suggest you do the following:

    variable "name_map" {
      default = [
        {
          name = "devbox"
          devices = [
            {
              device_name = "/dev/xvdg",
              volume_size = 900
            },
            {
              device_name = "/dev/xvdh",
              volume_size = 200
            }
          ]
          group         = "hosts"
          instance_type = "m5a.2xlarge"
        },
        {
          name = "devbox2"
          devices = [
            {
              device_name = "/dev/xvdg",
              volume_size = 900
            },
            {
              device_name = "/dev/xvdh",
              volume_size = 200
            }
          ]
          group         = "hosts"
          instance_type = "m5a.2xlarge"
        }
      ]
    }
    

    Note, the device_name and the volume_size are grouped together. Now we can use a simple foor loop where we don’t have to rely on indexing:

    resource "aws_instance" "node" {
      count = length(var.name_map)
    
      instance_type = var.name_map[count.index].instance_type
      ami           = "..." # fill in a valid AMI name
    
      dynamic "ebs_block_device" {
        # Notice the i variable (index) was dropped here
        for_each = [for device in var.name_map[count.index].devices : {
          device_name = device.device_name,
          volume_size = device.volume_size
        }]
        content {
          device_name           = ebs_block_device.value.device_name
          volume_type           = "gp2"
          volume_size           = ebs_block_device.value.volume_size
          delete_on_termination = true
        }
      }
    }
    
    Login or Signup to reply.
  2. I would nest your map further to create something like this:

    name_map = [
      {
        name         = "devbox"
        root_block_device = {
          ...settings
        }
        ebs_block_devices = toSet([
          {
            name = "/dev/xvdg"
            size = "900"
          },{
            name = "/dev/xvdh"
            size = "200"
          }
        ])
        group        = "hosts"
        instance_type = "m5a.2xlarge"
      },
      ...
    ]
    

    and then in your resource code you can loop over the set for each instance:

    resource "aws_instance" "instance" {
      count = length(var.name_map)
    
      ...
    
      root_block_device {
        ...settings from var.name_map[count.index].root_block_device
      }
    
      dynamic "ebs_block_device" {
        for_each = var.name_map[count.index].ebs_block_devices
    
        content {
          device_name = ebs_block_device.value.name
          volume_size = ebs_block_device.value.size
        }
      }
    }
    

    If you want the root volume to persist post termination I would suggest adding an EBS root volume, otherwise you can ignore the root_block_device and it will create an ephemeral device that contains the image.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search