LAB23_AWS_Terraform for_each: Map-Driven Resource Creation

Terraform for_each: Map-Driven Resource Creation

☁ AWS ⭐ Beginner for_each each.key / each.value toset() for expressions

Scenario

Your organisation needs to create multiple IAM users for a new development team, each with different access levels. You also need to create multiple S3 buckets for different business units with different configurations. Instead of hardcoding individual resource blocks, you want to drive everything from a map variable so any future additions only require a change to the variable.

Your Objectives
  • Use for_each = toset(list) to create IAM users from a list of usernames.
  • Use for_each = map to create S3 buckets with per-bucket configurations (versioning on/off, tags).
  • Use each.key and each.value to access the current iteration's key and value.
  • Use a for expression in an output to build a map of username → IAM ARN.
  • Demonstrate why removing one user from the map does not affect others.

Additional Context

for_each with a map gives you control over the state key. Each resource is stored as aws_s3_bucket.buckets["analytics"] instead of aws_s3_bucket.buckets[0]. This means deleting one key from the map only destroys that specific resource — not all subsequent ones.

Use toset() when iterating over a list with for_each — it converts the list to a set, which eliminates duplicates and provides stable keys based on the value itself (not its index).

versions.tf

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 5.0" }
  }
}

variables.tf

variable "aws_region" { type = string; default = "us-east-1" }

# A simple list — for IAM users where all users are treated identically.
variable "dev_team_users" {
  description = "List of IAM usernames for the development team."
  type        = list(string)
  default     = ["alice", "bob", "carol"]
}

# A map — for S3 buckets where each bucket has different settings.
variable "team_buckets" {
  description = "Map of S3 bucket logical names to their configurations."
  type = map(object({
    versioning = bool
    team       = string
  }))
  default = {
    "analytics" = { versioning = true,  team = "data" }
    "frontend"  = { versioning = false, team = "web"  }
    "backups"   = { versioning = true,  team = "ops"  }
  }
}

main.tf

# main.tf

provider "aws" {
  region = var.aws_region
}

data "aws_caller_identity" "current" {}

# for_each with toset(list):
# each.key = each.value = the username string
# State addresses: aws_iam_user.dev["alice"], aws_iam_user.dev["bob"], etc.
# Removing "bob" from the list only destroys aws_iam_user.dev["bob"].
resource "aws_iam_user" "dev" {
  for_each = toset(var.dev_team_users)

  name = each.key   # the username
  path = "/dev-team/"

  tags = { Team = "dev", ManagedBy = "terraform" }
}

# for_each with a map:
# each.key   = the map key (e.g., "analytics")
# each.value = the object value (e.g., { versioning = true, team = "data" })
resource "aws_s3_bucket" "team_buckets" {
  for_each = var.team_buckets

  bucket = "${each.key}-bucket-${data.aws_caller_identity.current.account_id}"

  tags = {
    Name      = "${each.key}-bucket"
    Team      = each.value.team
    ManagedBy = "terraform"
  }
}

# Conditionally enable versioning based on the map value.
# for_each is used again — one versioning resource per bucket.
resource "aws_s3_bucket_versioning" "team_buckets" {
  for_each = var.team_buckets

  bucket = aws_s3_bucket.team_buckets[each.key].id

  versioning_configuration {
    # Use each.value to toggle versioning per bucket.
    status = each.value.versioning ? "Enabled" : "Suspended"
  }
}

outputs.tf

# for expression: transform a map-keyed resource collection into a new map.
# Result: { "alice" = "arn:aws:iam::...:user/dev-team/alice", ... }
output "dev_user_arns" {
  description = "Map of username to IAM ARN for all dev team users."
  value       = { for username, user in aws_iam_user.dev : username => user.arn }
}

output "bucket_names" {
  description = "Map of logical bucket name to actual S3 bucket name."
  value       = { for k, v in aws_s3_bucket.team_buckets : k => v.id }
}

Workflow Commands

terraform init
terraform apply

# View state addresses — note the map keys, not numeric indices
terraform state list

# Output the user ARN map
terraform output -json dev_user_arns

# Simulate removing a user — only that user's resources are destroyed
# (edit variables.tf, remove "bob", then:)
terraform plan  # shows: 1 to destroy (aws_iam_user.dev["bob"])

terraform destroy
✓ Expected Output After Apply
Apply complete! Resources: 9 added, 0 changed, 0 destroyed.

Outputs:

bucket_names = {
  "analytics" = "analytics-bucket-123456789012"
  "backups"   = "backups-bucket-123456789012"
  "frontend"  = "frontend-bucket-123456789012"
}
dev_user_arns = {
  "alice" = "arn:aws:iam::123456789012:user/dev-team/alice"
  "bob"   = "arn:aws:iam::123456789012:user/dev-team/bob"
  "carol" = "arn:aws:iam::123456789012:user/dev-team/carol"
}