Back to Blog

Mastering Advanced HCL: Loops, Imports, and Dynamic Infrastructure

Refactoring static HCL into production-grade Terraform using data sources, local values, conditional feature toggles, for_each loops, and native declarative imports.

1. Dynamic Context with Data Sources & Locals

Hardcoding AWS Account IDs or Region names creates fragile configurations. By leveraging AWS Data Sources, infrastructure context is queried live during runtime and fed directly into centralized local tag blocks.

# data.tf
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

# locals.tf
locals {
  common_tags = {
    Environment = var.environment
    ManagedBy   = "Terraform"
    CreatedBy   = "Alain Puron"
    Region      = data.aws_region.current.region
    AccountId   = data.aws_caller_identity.current.account_id
  }
}

2. DRY Resource Generation with `for_each`

Rather than duplicating resource blocks for every CloudWatch Log Group, a single dynamic block driven by a string set variable processes multiple Lambda functions cleanly while maintaining individual state tracking.

# logging.tf
resource "aws_cloudwatch_log_group" "lambda_logs" {
  for_each          = var.lambda_functions
  name              = "/aws/lambda/${each.value}"
  retention_in_days = var.log_retention_days
  tags              = local.common_tags
}

3. Feature Flagging with Conditional Toggles

To control resource creation across environments without altering code structure, conditional evaluation (`count`) is paired with boolean flag variables.

# alarms.tf
resource "aws_cloudwatch_metric_alarm" "visitor_lambda_errors" {
  count               = var.enable_alerts ? 1 : 0
  alarm_name          = "visitor-lambda-error-alarm"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 1
  metric_name         = "Errors"
  namespace           = "AWS/Lambda"
  period              = 300
  statistic           = "Sum"
  threshold           = 1
  dimensions          = { FunctionName = "increment-visitor-counter" }
  tags                = local.common_tags
}

4. Adopting Existing AWS Resources via Declarative Imports

When AWS services auto-generate implicit resources (such as Lambda creating log groups upon first execution), standard deployments fail with a ResourceAlreadyExistsException. Using native HCL import blocks avoids complex CLI shell-escaping and safely adopts unmanaged resources into state.

# imports.tf
import {
  to = aws_cloudwatch_log_group.lambda_logs["increment-visitor-counter"]
  id = "/aws/lambda/increment-visitor-counter"
}

import {
  to = aws_cloudwatch_log_group.lambda_logs["send-contact-email"]
  id = "/aws/lambda/send-contact-email"
}

5. Key Technical Takeaways

  • Dynamic Output Mapping: Used dynamic `for` expressions in outputs (`[for lg in aws_cloudwatch_log_group.lambda_logs : lg.arn]`) to extract attributes across iterated resource collections.
  • State Collision Resolution: Solved state conflicts declaratively directly in HCL code rather than relying on brittle CLI commands.
  • Tag Standardization: Standardized resource tagging across the entire stack using `local.common_tags`.