Exam at a Glance
Objective 4 — All 8 Sub-Objectives
Objective 4 is the largest single objective on the exam. Every sub-objective is covered on this page.
- 4a. Understand resource and data blocks — managing vs reading infrastructure
- 4b. Understand resource attributes and cross-resource references — implicit and explicit dependencies
- 4c. Understand input variables and output values — precedence order, validation, sensitivity
- 4d. Understand complex types — collections (list, set, map) and structural types (object, tuple)
- 4e. Understand built-in expressions and functions — interpolation, conditionals, for, splat, dynamic blocks
- 4f. Understand resource meta-arguments —
depends_on,lifecyclerules NEW 004 - 4g. Understand custom conditions — precondition, postcondition, check blocks NEW 004
- 4h. Understand sensitive data handling — sensitive flag, ephemeral values, state exposure NEW 004
Why HCL Matters
The Foundation of Everything
Every Terraform workflow starts with writing HCL. Understanding resource blocks, variables, expressions, and dependencies is essential before you can reason about any real-world configuration.
What's New in 004
- Lifecycle sub-arguments (
replace_triggered_by) - Custom conditions via
precondition/postcondition - The standalone
checkblock - Sensitive & ephemeral value handling
- Write-only provider arguments
Key Themes to Remember
- resource = Terraform manages lifecycle
- data = read-only, external source
- Variable precedence order (6 levels)
- sensitive ≠ encrypted in state
- precondition = before, postcondition = after, check = advisory
This Page Covers
- 8 sub-objectives: 4a → 4h
- 10-question adaptive quiz
- 8 flip flashcards
- 6 memory hooks
- Study advisor with 5 categories
HCL Block Types Summary
| Block Type | Purpose | Lifecycle | Reference Syntax |
|---|---|---|---|
resource | Declares infrastructure to create/manage | Terraform owns it (plan, apply, destroy) | aws_instance.web.id |
data | Reads existing/external data | Read-only at plan/apply time | data.aws_ami.ubuntu.id |
variable | Input parameters for the module | Set at plan time by caller | var.instance_type |
output | Exposes values after apply | Emitted post-apply, queryable | terraform output <name> |
locals | Named intermediate expressions | Computed during plan | local.common_tags |
check | Non-blocking assertions | Runs post-apply; warns only | N/A (standalone) |
4a — Resource and Data Blocks
Objective 4aresource block
Declares infrastructure that Terraform creates, manages, and destroys. The resource type (first label) identifies the provider resource, and the local name (second label) is used to reference it elsewhere in the config.
resource "aws_instance" "web" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t3.micro" tags = { Name = "WebServer" } }
data block
Reads existing or external data without managing its lifecycle. Terraform cannot create or destroy data sources — it only reads them.
data "aws_ami" "ubuntu" { most_recent = true owners = ["099720109477"] # Canonical filter { name = "name" values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"] } }
Reference Syntax
| Block | Reference Pattern | Example |
|---|---|---|
| resource | <type>.<name>.<attribute> | aws_instance.web.id |
| data | data.<type>.<name>.<attribute> | data.aws_ami.ubuntu.id |
4b — Resource Attributes & Cross-Resource References
Objective 4bImplicit Dependencies
When one resource references an attribute of another, Terraform automatically creates a dependency — it will create the referenced resource first. No explicit declaration needed.
resource "aws_instance" "web" { # Terraform knows: create aws_subnet.main BEFORE this instance subnet_id = aws_subnet.main.id ami = "ami-0c55b159cbfafe1f0" instance_type = "t3.micro" }
Explicit Dependencies (depends_on)
Use when a dependency exists that is not captured by attribute references — for example, an IAM policy that must be attached before a resource can function, even though no attribute links them.
resource "aws_s3_object" "upload" { bucket = aws_s3_bucket.artifacts.bucket key = "data.json" source = "./data.json" depends_on = [aws_s3_bucket_policy.artifacts] }
Rule of thumb: Use implicit references where possible; reserve depends_on for hidden dependencies only. Overuse of depends_on can cause unnecessary re-plans.
Visualizing the Graph
Run terraform graph to output a DOT-format dependency graph that can be rendered with Graphviz or online tools to visualize all dependencies.
4c — Variables and Outputs
Objective 4cInput Variable Declaration
variable "instance_type" { type = string default = "t3.micro" description = "EC2 instance type" validation { condition = contains(["t3.micro", "t3.small"], var.instance_type) error_message = "Must be t3.micro or t3.small." } }
Variable Precedence (Lowest → Highest)
| Level | Source | Notes |
|---|---|---|
| 1 (lowest) | default in variable block | Fallback if nothing else set |
| 2 | terraform.tfvars | Auto-loaded if present |
| 3 | *.auto.tfvars | Auto-loaded, alphabetical order |
| 4 | -var-file flag | Explicit file on CLI |
| 5 | -var flag | Inline on CLI |
| 6 (highest) | TF_VAR_<name> env var | Environment variable |
Output Values
output "instance_ip" { value = aws_instance.web.public_ip description = "Public IP of the web server" sensitive = false }
Outputs appear after terraform apply, are accessible via terraform output, and can be consumed by parent modules or other configurations.
4d — Complex Types
Objective 4dPrimitive Types
Collection Types
| Type | Ordered? | Duplicates? | Example |
|---|---|---|---|
list(string) | Yes | Yes | ["a", "b", "a"] |
set(string) | No | No (unique only) | toset(["a", "b"]) |
map(string) | No | Keys unique | { env = "prod" } |
Structural Types
# object — fixed schema, named attributes variable "server_config" { type = object({ name = string port = number enabled = bool }) } # tuple — fixed length, positional, mixed types variable "record" { type = tuple([string, number, bool]) }
Type Conversion Functions
Use any type to disable type checking — Terraform will infer the type from the value.
4e — Expressions and Functions
Objective 4eString Interpolation
name = "server-${var.environment}-${var.region}"Conditional Expression
instance_type = var.env == "prod" ? "t3.large" : "t3.micro"
For Expressions
# List comprehension upper_list = [for s in var.names : upper(s)] # With filter condition long_names = [for s in var.names : s if length(s) > 5] # Map comprehension upper_map = {for k, v in var.tags : k => upper(v)}
Splat Expression
# Collect all IDs from a list of resources
all_ids = aws_instance.server[*].idDynamic Block
Use dynamic to generate repeated nested blocks from a collection — avoids copy-paste.
dynamic "ingress" { for_each = var.allowed_ports content { from_port = ingress.value to_port = ingress.value protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } }
Key Built-in Functions
| Function | Purpose |
|---|---|
length(list) | Count of elements |
join(sep, list) | Join list into string |
split(sep, str) | Split string into list |
lookup(map, key, default) | Safe map key lookup |
merge(map1, map2) | Combine two maps |
flatten(list) | Flatten nested lists |
toset(list) | Convert list to set (dedup) |
keys(map) | List of map keys |
values(map) | List of map values |
contains(list, val) | Check if element exists |
element(list, index) | Get element by index (wraps) |
cidrsubnet(prefix, bits, num) | Calculate subnet CIDR |
4f — Resource Dependencies & Lifecycle NEW 004
Objective 4fdepends_on
Forces Terraform to create/update the listed resources before this one. Use when a real dependency exists but no attribute reference makes it visible to Terraform.
resource "aws_instance" "app" { ami = data.aws_ami.ubuntu.id instance_type = "t3.micro" # IAM role must be fully propagated before instance starts depends_on = [aws_iam_role_policy_attachment.app_policy] }
lifecycle Meta-Arguments
| Argument | Effect | Use Case |
|---|---|---|
create_before_destroy | New resource is created before old is destroyed | Zero-downtime replacement (e.g., Auto Scaling Groups, SSL certs) |
prevent_destroy | Error raised if destroy is attempted | Protect databases, stateful resources from accidental deletion |
ignore_changes | Ignores drift on listed attributes | Attributes managed outside Terraform (e.g., tags set by automation) |
replace_triggered_by | Forces resource replacement when referenced value changes | Replace EC2 instance when launch template version changes |
resource "aws_instance" "web" { ami = data.aws_ami.app.id instance_type = "t3.micro" lifecycle { create_before_destroy = true prevent_destroy = true ignore_changes = [tags, user_data] replace_triggered_by = [aws_launch_template.app.latest_version] } }
4g — Custom Conditions NEW 004
Objective 4gprecondition
Checked before resource create or update. If the condition is false, the plan is aborted with your custom error message. Acts as a guard/input validator.
postcondition
Checked after resource create or update. Validates that the resulting resource state meets your requirements. Useful for asserting API-returned values.
resource "aws_instance" "app" { ami = var.image_id instance_type = "t3.micro" lifecycle { precondition { condition = var.image_id != "" error_message = "Image ID must not be empty." } postcondition { condition = self.public_ip != "" error_message = "Instance must have a public IP." } } }
check block (Terraform 1.5+)
A standalone block that runs assertions outside of any resource lifecycle. Non-blocking — a failing check produces a warning, not an error, so the plan/apply continues.
check "health_check" { data "http" "endpoint" { url = "https://${aws_lb.main.dns_name}/health" } assert { condition = data.http.endpoint.status_code == 200 error_message = "Health check endpoint returned unexpected status." } }
Key distinction: precondition / postcondition → blocking (fails plan/apply). check block → advisory (warns only, does not fail).
4h — Sensitive Data Handling NEW 004
Objective 4hsensitive = true
Marks a variable or output as sensitive. Terraform redacts the value in plan and apply output, showing (sensitive value) instead.
variable "db_password" { type = string sensitive = true } output "connection_string" { value = "...${var.db_password}..." sensitive = true # must mark output sensitive too }
Critical: sensitive = true hides values in terminal output, but they are still stored in plaintext in Terraform state. Secure your state backend.
Ephemeral Values (Terraform 1.10+)
Ephemeral values are not written to state at all. Use for short-lived secrets (tokens, temporary passwords) that should never persist.
Write-Only Arguments
Some provider resource attributes are designated as write-only — Terraform accepts the value and sends it to the provider but does not store it in state. Useful for passwords and API keys.
HashiCorp Vault Integration
data "vault_generic_secret" "db" { path = "secret/db/production" } resource "aws_db_instance" "main" { password = data.vault_generic_secret.db.data["password"] # ...other attributes... }
Environment Variable Injection
# Pass secrets without writing them to any file $ TF_VAR_db_password=mysecret terraform apply # NEVER do this (commits secret to VCS): # db_password = "mysecret" in terraform.tfvars
Memory Hooks
Six sticky phrases to lock in the most exam-tested concepts from Objective 4.
resource block hands Terraform full lifecycle ownership — it creates, updates, and destroys. A data block is purely read-only; Terraform never modifies the external thing it reads. If the exam asks who "owns" the lifecycle, resource does.TF_VAR_ environment variable always wins — it overrides every file-based and CLI flag setting. The default in the variable block is the fallback of last resort. Memorize this order; the exam tests it directly.create_before_destroy = true it flips the order: spin up the replacement first, then tear down the old one. Essential for load-balanced instances, SSL certificates, and any resource that must stay alive during replacement.check block is a fire alarm — it warns loudly but doesn't lock you out.sensitive = true only redacts values from terminal/log output. It does nothing to protect state. State files store sensitive values in plaintext JSON. You must secure state independently — use encrypted remote backends (S3 with KMS, Terraform Cloud, etc.).for expression: opening bracket type (list [] or map {}), the for keyword, iterator variable, source collection, colon, transformation, optional if filter. For maps use k, v and => instead of :. The if at the end is optional filtering.Practice Quiz
10 questions covering all 8 sub-objectives. Select an answer to see instant feedback.
resource block and a data block in Terraform?ubuntu of type aws_ami?aws_instance resource uses an IAM role that is attached via a separate aws_iam_role_policy_attachment resource. The instance does not reference any attribute of the attachment. What is the correct approach to ensure proper ordering?region has a default value of "us-east-1". It also appears in terraform.tfvars as "us-west-2", in a prod.auto.tfvars file as "eu-west-1", and is passed via -var region=ap-southeast-1 on the CLI. Which value will Terraform use?set(string). Which of the following is a guaranteed characteristic of this type?aws_db_instance database can never be accidentally deleted by terraform destroy. Which lifecycle argument achieves this?check block assertion fails during terraform apply. What is the result?sensitive = true. A developer runs terraform plan. Which statement is accurate?var.names (a list of strings)?replace_triggered_by lifecycle argument do?Flashcards
Click any card to flip it. 8 cards covering all key concepts from Objective 4.
type.name.attr. Data reference: data.type.name.attr.subnet_id = aws_subnet.main.id) — Terraform auto-detects the dependency. depends_on: use for hidden dependencies where no attribute links the resources (e.g., IAM policy propagation) so Terraform cannot infer the ordering itself.2. terraform.tfvars
3. *.auto.tfvars
4. -var-file flag
5. -var flag
6. TF_VAR_ environment variable (highest)
set(T): unordered, no duplicates, no index access — useful for for_each.
map(T): unordered, key-value pairs, key access (map["key"]), keys must be unique.
resource_type.name[*].attribute. Example: aws_instance.server[*].id returns a list of all server IDs. Equivalent to [for s in aws_instance.server : s.id].Study Advisor
Select the area where you need the most help. Get targeted review advice.
Official Resources
Primary sources for Terraform Associate 004 exam preparation.
-
Official exam guide, study materials, and tutorial collection from HashiCorp.
-
Complete reference for HCL syntax — resource, data, variable, output, expressions, and all built-in functions.
-
Deep-dive into for expressions, splat, conditionals, dynamic blocks, and type system.
-
How sensitive = true works, what it protects, and what it does not.
-
Complete reference for depends_on, lifecycle, count, for_each, and provider meta-arguments.
-
precondition, postcondition, and check block documentation with examples.
Practice Tests
-
Full-length timed practice exams with objective-by-objective performance tracking.
Series Navigation
Page 1 of 5
Terraform Fundamentals — IaC concepts, providers, state, core workflow, backends
Page 2 of 5 — You are here
Configuration Language (HCL) — Objective 4, all 8 sub-objectives
Page 3 of 5
Terraform State — state management, locking, remote backends, workspaces
Page 4 of 5
Modules — module structure, registry, versioning, module composition
Page 5 of 5
Workflows & Cloud — Terraform Cloud, CLI, plan/apply, import, testing