Skip to main content

On This Page

Mastering the Mental Shift: Why Terraform HCL Differs from Standard Coding

3 min read
Share

These articles are AI-generated summaries. Please check the original sources for full details.

Terraform HCL for Developers: Why It Feels Familiar and Strange

HashiCorp Configuration Language (HCL) acts as a hybrid between a configuration language and a programming language. Unlike Python or JavaScript, block order in HCL does not define execution order because the system builds a dependency graph from the configuration.

Why This Matters

Developers often struggle with HCL because it looks like code but behaves like a schema. The technical reality is that Terraform must map configuration objects to persistent state to track real-world infrastructure, meaning identical code can yield different results based on existing state files. This state-dependency creates a boundary where HCL excels at describing shape and identity but fails at complex business logic or heavy data wrangling.

Key Insights

  • Graph-based execution: Terraform resolves values by walking a dependency graph rather than sequential script processing, meaning block order is irrelevant.
  • Structural Blocks vs. Arguments: Arguments assign values while blocks represent schema-defined structures; this distinction explains why ‘dynamic’ blocks are required for structural generation.
  • Pythonic Expressions: HCL features functional list and map comprehensions, where square brackets produce tuples and curly braces produce objects via the ’=>’ operator.
  • Stable Instance Identity: Using for_each with toset() ensures resources are indexed by stable keys, preventing the destructive re-indexing issues common with list-based count operations.
  • State as the Hidden Half: HCL configuration is only one part of the model; persistent state maps code to real-world resources and tracks metadata for planning.

Working Examples

Distinction between arguments (value assignment) and blocks (structural configuration).

resource "aws_instance" "web" {
  ami = "ami-1234567890" # argument
  instance_type = "t3.micro" # argument
  tags = {
    Name = "web"
  }
  ebs_block_device { # nested block
    device_name = "/dev/sda1"
    volume_size = 50
  }
}

HCL for expressions for transforming data into lists or maps.

subnet_ids = [for s in aws_subnet.private : s.id]
subnet_map = {
  for s in aws_subnet.private : s.tags["Name"] => s.id
}

Using for_each with a set to ensure stable resource identity based on keys.

locals {
  environments = toset(["dev", "staging", "prod"])
}
resource "aws_s3_bucket" "env" {
  for_each = local.environments
  bucket = "my-app-${each.key}"
}

Practical Applications

  • Multi-Environment Provisioning: Use for_each with maps to maintain stable resource addresses. Pitfall: Using list-indexed identity (count) which triggers resource recreation if the list order changes.
  • Infrastructure Refactoring: Use ‘moved’ blocks when renaming objects to preserve state identity. Pitfall: Manually renaming HCL resources without state migration, leading to Terraform attempting to delete and recreate infrastructure.
  • Dynamic Resource Generation: Use ‘dynamic’ blocks to generate repeated nested configuration like security group rules. Pitfall: Excessive use of dynamic blocks for complex branching, making the configuration unreadable compared to standard programming languages.

References:

Continue reading

Next article

European Commission Cloud Breach: Analyzing the Cloud Security Complexity Gap

Related Content