Kanji
・Cloud engineer / freelance ・Born in 1993 ・From Ehime / Living in Shibuya, Tokyo ・5 years AWS experience Profile details
Table of Contents
aws_eventbridge_*
moved
aws_subnets
aws_cloudwatch_log_groups
aws_eventbridge_rules
check
count
for_each = toset(...)
hashicorp/awscc
data "external"
Back in March 2024 when I published the first edition of this guide, I was designing cross-account, multi-repository Terraform modules for enterprise AWS environments. The goal was to dynamically fetch multiple existing resources (VPCs, subnets, CloudWatch Log Groups, CodePipelines) via Terraform Data Sources and bulk-deploy monitoring alarms and EventBridge failure notifications.
However, operating these modules in production revealed 3 critical traps that broke down under scale.
First, whenever a plural Data Source was missing in hashicorp/aws , I hastily created Bash scripts with data "external" ( aws cli + jq ). While it worked on local macOS, moving CI/CD to lightweight GitHub Actions or Terraform Cloud runners caused widespread terraform plan failures because runners lacked jq or subshells lost cross-account AssumeRole credentials. Later investigation revealed that AWS Provider v6 added native plural Data Sources ( aws_dynamodb_tables , aws_ecs_clusters , aws_s3_buckets , aws_eventbridge_rules ), and the official AWS Cloud Control API provider ( hashicorp/awscc ) allowed fetching resources like CodePipeline purely in HCL without writing shell scripts.
hashicorp/aws
aws cli
jq
terraform plan
aws_dynamodb_tables
aws_ecs_clusters
aws_s3_buckets
Second, using data "external" triggers strict Go type constraints: Result Error: json: cannot unmarshal array into Go value of type string . The external provider strictly demands a flat map(string) . Serializing arrays into strings with jq '@json' and decoding in HCL with jsondecode() is required, but empty query results ( [] ) crashed jsondecode("") if the script returned an empty string.
Result Error: json: cannot unmarshal array into Go value of type string
external
map(string)
jq '@json'
jsondecode()
[]
jsondecode("")
Third, iterating plural Data Source lists with count = length(local.target_ids) led to index-shift outages. In an environment with 5 pipelines where EventBridge failure rules were generated via count , removing pipeline-02 caused all subsequent rules ( index[1] through index[4] ) to be destroyed and recreated simultaneously. Dynamic loops must always bind immutable resource identifiers as state keys using for_each = toset(...) .
count = length(local.target_ids)
pipeline-02
index[1]
index[4]
This guide outlines the modern 3-layer architecture for safely fetching multiple AWS resources using Terraform AWS Provider v6 (v6.72+), complete with HCL examples and EventBridge best practices.
Terraform Data Sources dynamically query read-only metadata from infrastructure managed outside the current module or in separate repositories.
terraform_remote_state
aws_caller_identity
aws_region
aws_ami
awscc_codepipeline_pipelines
hashicorp/external
terraform { required_version = ">= 1.5.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 6.72" } } } # 1. Fetch all log groups matching prefix data "aws_cloudwatch_log_groups" "prd_lambda_logs" { log_group_name_prefix = "/aws/lambda/prd-" } # 2. Guard against accidental 0-result deletion using check block (Terraform 1.5+) check "verify_prd_lambda_log_groups_exist" { assert { condition = length(data.aws_cloudwatch_log_groups.prd_lambda_logs.log_group_names) > 0 error_message = "No CloudWatch log groups found matching /aws/lambda/prd-. Check prefix or region." } } # 3. Bind resource names as state keys using for_each = toset(...) resource "aws_cloudwatch_log_metric_filter" "error_count" { for_each = toset(data.aws_cloudwatch_log_groups.prd_lambda_logs.log_group_names) name = "error-count-${replace(trimprefix(each.value, "/aws/lambda/"), "/", "-")}" pattern = "?ERROR ?Exception ?Traceback" log_group_name = each.value metric_transformation { name = "ErrorCount-${replace(trimprefix(each.value, "/aws/lambda/"), "/", "-")}" namespace = "Custom/LambdaErrors" value = "1" } }
# Fetch EventBridge rules using native plural Data Source data "aws_eventbridge_rules" "app_rules" { name_prefix = "prd-order-" event_bus_name = "default" } # Attach SNS alert targets across all discovered rules resource "aws_eventbridge_target" "sns_alert" { for_each = toset(data.aws_eventbridge_rules.app_rules.names) rule = each.value event_bus_name = "default" arn = var.alert_sns_topic_arn } # Zero-downtime state migration from legacy aws_cloudwatch_event_rule moved { from = aws_cloudwatch_event_rule.legacy_rule to = aws_eventbridge_rule.app_rule }
aws_cloudwatch_event_*
Master conversation skills with realistic AI phone training and 5-axis feedback. Includes 16-lesson interactive academy and studio AI portraits.
Effortlessly snap receipts for instant AI itemization. Integrated with LINE for seamless group expense tracking and pain-free settlement.
Interactive AI personas (Fortune Teller, Butler, Bartender) host real-time games, challenges, and conversation starters for unforgettable gatherings.
Get the complete configuration pack featured in this article: universal AGENTS.md, deterministic hooks.json, Git safety guardrails, and validation scripts tested across Antigravity, Kiro, Claude Code, and Codex.