Chapter 2 of 24

HCL as a Language

Turn typed inputs into a stable, reviewable subnet map before a provider ever sees them.

Learning objectives

Values first, resources second

Primitives

string, number, and bool are scalar values.

Structured values

Objects name fields; tuples keep positions. Maps and sets model keyed and unique collections.

null

It means omit an optional value, not an empty string or zero.

Unknown

A new resource attribute is unavailable until apply; it is not false or empty.

public_names = [for s in var.subnets : s.name if s.public]
subnet_ids = aws_subnet.app[*].id
name = var.enabled ? "app" : null
labels = merge({ team = "platform" }, var.labels)

Use for to reshape values, a conditional for one deliberate choice, splats for one attribute across a collection, and built-ins such as lower, merge, try, coalesce, tolist, and sort instead of duplicated expressions.

Stable keys matter: a map keyed by a normalized subnet name makes later for_each addresses predictable.

AWS context

This provider-free lab costs nothing. Its subnet-shaped values prepare for AWS VPC resources later; a declared map is not a subnet and never needs credentials.

aws sts get-caller-identity --profile learning
aws configure get region --profile learning

Lab: normalize subnet declarations $0

Open labs/02-hcl/main.tf. It accepts objects and emits a lowercase-keyed map with explicit defaults and sorted tag lists.

Terraform

cd learn-terraform/labs/02-hcl
terraform init -backend=false
terraform console
terraform plan

OpenTofu

cd learn-terraform/labs/02-hcl
tofu init -backend=false
tofu console
tofu plan

At the console, try local.normalized_subnets, type(var.subnets), and [for s in var.subnets : s.name if s.public].

Read the output as a contract

Expect no resources and one output. Check that keys are app-a and web-b, web-b.public is true, and tag order is stable. (known after apply) is normal only when an output depends on a resource result.

Failure drill: duplicate normalized key

Change Web-B to APP-A. The for expression cannot create two map entries with the same key, so plan fails. Restore the distinct name; do not accept an unstable list index instead.

Do not stringify everything: an empty string is not null; an API may treat it as a real invalid value.

Verify

  1. Run terraform plan or tofu plan.
  2. Confirm zero resource actions and the normalized output.
  3. Inspect the same local with terraform console or tofu console.
  4. Confirm no AWS provider, credentials, or billable resource appears.

Cleanup

No apply is required. Remove local initialization files only if you want a fresh directory: rm -rf .terraform .terraform.lock.hcl. There is no cloud cleanup.

Quick check

When should a value be null?

When an optional argument should be omitted, rather than set to an empty or placeholder value.

Why use a map for later resources?

A named key gives a stable for_each address; a list index can shift when an item is inserted.

Can an unknown value be treated as false?

No. It is only unavailable until its dependency is created.

Recap and next

HCL is a typed expression language. Normalize values once, keep keys stable, and test expressions in the console before they drive resources.

Next: Chapter 3, Resources and the Dependency Graph.