Migrating from count to for_each
Prior to the 0.12 release of Terraform you could manage a group of similar resources using count
. For example, you might have a manifest that looks like this:
provider "aws" {
region = "us-east-2"
}
locals {
user_names = ["maggie", "milly", "molly", "may"]
}
resource "aws_iam_user" "example" {
count = length(local.user_names)
name = local.user_names[count.index]
}
This will create resources aws_iam_user.example[0]
, aws_iam_user.example[1]
, etc. The problem comes if you want to delete any user but the last in the list. This will cause the subsequent resource indices to change, which will trigger terraform to delete and re-create resources needlessly. In version 0.12 a new looping construct, for_each
, was introduced, allowing us to write our manifest as follows:
provider "aws" {
region = "us-east-2"
}
locals {
user_names = {
"maggie" = {}
"milly" = {}
"molly" = {}
"may" = {}
}
}
resource "aws_iam_user" "example" {
for_each = local.user_names
name = each.key
}
Now the created resources are named after the corresponding key, aws_iam_user.example["maggie"]
, aws_iam_user.example["milly"]
, etc. and we can delete any user without affecting the others. Note that the local variable user_names
has changed from a list to a map. If we want to override certain attributes of the created resources, we can add these to our map:
locals {
user_names = {
"maggie" = {path = "/staff/"}
"milly" = {path = "/staff/"}
"molly" = {path = "/staff/"}
"may" = {path = "/robot/"}
}
}
We can then reference them through each.value
:
resource "aws_iam_user" "example" {
for_each = local.user_names
name = each.key
path = each.value.path
}
The question arises: what if we started out with a manifest using count
, and want to transition to the new for_each
? If we simply update the manifest, we will trigger the create/destroy operations we are working to avoid. The answer is to rename the resources in the existing terraform state. For the above example, this simple bash script would do the trick:
#!/bin/bash
declare -i N=0
while read U; do
terraform state mv "aws_iam_user.example[$N]" "aws_iam_user.example[\"$U\"]"
(( N++ ))
done <<EOT
maggie
milly
molly
may
EOT
N.B. the resource names input to the shell must be in the exact same order as they appeared in the original terraform manifest.