Skip to content

Instantly share code, notes, and snippets.

@brikis98
Last active March 14, 2023 23:43
Show Gist options
  • Star 57 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save brikis98/f3fe2ae06f996b40b55eebcb74ed9a9e to your computer and use it in GitHub Desktop.
Save brikis98/f3fe2ae06f996b40b55eebcb74ed9a9e to your computer and use it in GitHub Desktop.
A hacky way to create a dynamic list of maps in Terraform
# The goal: create a list of maps of subnet mappings so we don't have to statically hard-code them in aws_lb
# https://www.terraform.io/docs/providers/aws/r/lb.html#subnet_mapping
locals {
# These represent dynamic data we fetch from somewhere, such as subnet IDs and EIPs from a VPC module
subnet_ids = ["subnet-1", "subnet-2", "subnet-3"]
eips = ["eip-1", "eip-2", "eip-3"]
}
# Here's the hack! The null_resource has a map called triggers that we can set to arbitrary values.
# We can also use count to create a list of null_resources. By accessing the triggers map inside of
# that list, we get our list of maps! See the output variable below.
resource "null_resource" "subnet_mappings" {
count = "${length(local.subnet_ids)}"
triggers {
subnet_id = "${element(local.subnet_ids, count.index)}"
allocation_id = "${element(local.eips, count.index)}"
}
}
# And here's the result! We have a dynamic list of maps. I'm just outputting it here, but we should
# be able to take the same value and set it as the input to aws_lb's subnet_mapping param.
output "subnet_mappings" {
value = "${null_resource.subnet_mappings.*.triggers}"
}
@brikis98
Copy link
Author

brikis98 commented Mar 8, 2018

$ terraform init
$ terraform apply

null_resource.subnet_mappings[2]: Refreshing state... (ID: 5492324315923717110)
null_resource.subnet_mappings[0]: Refreshing state... (ID: 6342483238685385197)
null_resource.subnet_mappings[1]: Refreshing state... (ID: 238793807379032397)

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

subnet_mappings = [
    {
        allocation_id = eip-1,
        subnet_id = subnet-1
    },
    {
        allocation_id = eip-2,
        subnet_id = subnet-2
    },
    {
        allocation_id = eip-3,
        subnet_id = subnet-3
    }
]

@brikis98
Copy link
Author

brikis98 commented Mar 8, 2018

Update: it turns out that while this approach works for static values (i.e., hard-coded values, variables, local vars), it does not work in the general case for anything with "dynamic" or "computed" data such as a data source or resource. That's due to a Terraform limitation where it tries to validate the structure of the maps before doing any of the computations, and you'll get an error such as "required field is not set".

@zeyuye-airwallex
Copy link

👍

@bdashrad
Copy link

very useful thanks

@elvis-cai
Copy link

sweet trick

@rajrajen-juul
Copy link

https://www.terraform.io/docs/configuration/expressions.html#for-expressions For Loop is coming up natively in Terraform 0.12.x . Hope that solves our problem ..

@Behoston
Copy link

👍

@rivlinp
Copy link

rivlinp commented Jun 9, 2019

@brikis98 thanks for this trick. Turns out I was looking for exactly this but I want to use the o/p of subnet_mappings as a input to a variable inside a resource. You did mention that it wont work with a resource, so what are my options?

@atrepca
Copy link

atrepca commented Aug 8, 2019

Why not use the zipmap() function instead?

zipmap constructs a map from a list of keys and a corresponding list of values.

Currently doing this successfully with Terraform 0.12:

  dynamic "subnet_mapping" {
    for_each = zipmap(subnet_ids, eips)
    content {
      subnet_id     = subnet_mapping.key
      allocation_id = subnet_mapping.value
    }
  }

@brikis98
Copy link
Author

brikis98 commented Aug 8, 2019

This Gist was written pre-Terraform 0.12. It is no longer necessary.

@atrepca
Copy link

atrepca commented Aug 8, 2019

Sorry for not being clear, I didn’t mean this as a 0.12 thing, zipmap works with < 0.12 too, it was added in version 0.7.8 (changelog).

Thanks for posting this,

@fv-ian
Copy link

fv-ian commented Dec 6, 2019

True.. dynamic and for_each didn't exist before 0.12 either.

@waddles
Copy link

waddles commented Jan 31, 2020

For cases where zipmap() is not appropriate, for example you have a list(string) type and need to produce a list(object({})), try this syntax in Terraform 0.12:

locals {
  macs = [
    "aa:bb:cc:dd:ee:ff",
    "aa:bb:de:ad:be:ef",
  ]
}
module "servers" {
  source = "./app-cluster"
  workers = [
    for index, x in local.macs : {
      name = "node${index}"
      mac = x
    }
  ]
}

@jturver1
Copy link

👍

@Ak476024
Copy link

Ak476024 commented Jun 2, 2020

After I have switched from Terraform 0.11 to Terraform 0.12.. It's really got simple and easy to work with loops and other stuff. Try using this syntax

locals {
 parameters = [
           {     
                  name : "rds.force_ssl",
                  value : "1"
                  apply_method : "pending-reboot"
            },
            {     
                 name : "ssl",
                  value : "1",
                  apply_method : "pending-reboot"
            }
     ]
}

resource "aws_db_parameter_group" "parameter_group" {
  name   = var.name
  family = var.family

  dynamic "parameter" {
    for_each = var.parameters

    content {
      name   = parameter.value["name"]
      value  = parameter.value["value"]
      apply_method = parameter.value["apply_method"]
    }
  }
}`
```

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment