FlashGenius Logo FlashGenius
Terraform Associate 004 · Page 2 of 5

Terraform Configuration Language (HCL)

Terraform Associate 004 · Objective 4 — All 8 Sub-Objectives

resource · data · variables · outputs · complex types · expressions · lifecycle · custom conditions · sensitive

Study with Practice Tests →

Exam at a Glance

Exam
Terraform Associate (004)
Duration
1 hour
Format
Multiple Choice
Fee
$70.50
Terraform Version
1.12
This Page
Page 2 of 5

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, lifecycle rules 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 check block
  • 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 TypePurposeLifecycleReference Syntax
resourceDeclares infrastructure to create/manageTerraform owns it (plan, apply, destroy)aws_instance.web.id
dataReads existing/external dataRead-only at plan/apply timedata.aws_ami.ubuntu.id
variableInput parameters for the moduleSet at plan time by callervar.instance_type
outputExposes values after applyEmitted post-apply, queryableterraform output <name>
localsNamed intermediate expressionsComputed during planlocal.common_tags
checkNon-blocking assertionsRuns post-apply; warns onlyN/A (standalone)

4a — Resource and Data Blocks

Objective 4a

resource 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

BlockReference PatternExample
resource<type>.<name>.<attribute>aws_instance.web.id
datadata.<type>.<name>.<attribute>data.aws_ami.ubuntu.id

4b — Resource Attributes & Cross-Resource References

Objective 4b

Implicit 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 4c

Input 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)

LevelSourceNotes
1 (lowest)default in variable blockFallback if nothing else set
2terraform.tfvarsAuto-loaded if present
3*.auto.tfvarsAuto-loaded, alphabetical order
4-var-file flagExplicit file on CLI
5-var flagInline on CLI
6 (highest)TF_VAR_<name> env varEnvironment 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 4d

Primitive Types

string number bool

Collection Types

TypeOrdered?Duplicates?Example
list(string)YesYes["a", "b", "a"]
set(string)NoNo (unique only)toset(["a", "b"])
map(string)NoKeys 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

tostring()tolist()toset()tomap()tonumber()tobool()

Use any type to disable type checking — Terraform will infer the type from the value.

4e — Expressions and Functions

Objective 4e

String 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[*].id

Dynamic 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

FunctionPurpose
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 4f

depends_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

ArgumentEffectUse Case
create_before_destroyNew resource is created before old is destroyedZero-downtime replacement (e.g., Auto Scaling Groups, SSL certs)
prevent_destroyError raised if destroy is attemptedProtect databases, stateful resources from accidental deletion
ignore_changesIgnores drift on listed attributesAttributes managed outside Terraform (e.g., tags set by automation)
replace_triggered_byForces resource replacement when referenced value changesReplace 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 4g

precondition

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 4h

sensitive = 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.

Hook 1 — Objective 4a
resource = manage, data = read
A 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.
Hook 2 — Objective 4c
Variable precedence: Default < tfvars < auto.tfvars < -var-file < -var < TF_VAR_
Six levels, lowest to highest. A 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.
Hook 3 — Objective 4f
create_before_destroy = zero-downtime swap
By default Terraform destroys old → creates new. With 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.
Hook 4 — Objective 4g
precondition = before (guard), postcondition = after (verify), check = advisory (warns)
Think of a security guard: precondition checks your ID before letting you in (plan aborts if bad). postcondition checks the exit receipt after you leave (apply aborts if wrong). A check block is a fire alarm — it warns loudly but doesn't lock you out.
Hook 5 — Objective 4h
sensitive = hidden in output, NOT hidden in state
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.).
Hook 6 — Objective 4e
for expression syntax: [for item in list : transform(item) if condition]
The anatomy of a 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.

Question 1 of 10 · Objective 4a
Which statement correctly describes the difference between a resource block and a data block in Terraform?
Question 2 of 10 · Objective 4a
What is the correct syntax to reference an attribute from a data source named ubuntu of type aws_ami?
Question 3 of 10 · Objective 4b
An 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?
Question 4 of 10 · Objective 4c
A variable 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?
Question 5 of 10 · Objective 4d
A variable is declared as type set(string). Which of the following is a guaranteed characteristic of this type?
Question 6 of 10 · Objective 4f
A team wants to ensure an aws_db_instance database can never be accidentally deleted by terraform destroy. Which lifecycle argument achieves this?
Question 7 of 10 · Objective 4g
A check block assertion fails during terraform apply. What is the result?
Question 8 of 10 · Objective 4h
A variable is declared with sensitive = true. A developer runs terraform plan. Which statement is accurate?
Question 9 of 10 · Objective 4e
Which expression correctly creates a list of uppercase strings from the variable var.names (a list of strings)?
Question 10 of 10 · Objective 4f
What does the replace_triggered_by lifecycle argument do?

Flashcards

Click any card to flip it. 8 cards covering all key concepts from Objective 4.

Objective 4a
What is the key difference between a resource block and a data block?
Tap to flip
resource = Terraform manages the full lifecycle (create, update, destroy). data = read-only; Terraform reads existing or external data but never creates or destroys it. Resource reference: type.name.attr. Data reference: data.type.name.attr.
resource = own it · data = read it
Objective 4b
When should you use depends_on vs an implicit dependency?
Tap to flip
Implicit: use when you reference an attribute (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.
visible attr → implicit · hidden dep → depends_on
Objective 4f
What problem does create_before_destroy solve, and what is the default behavior without it?
Tap to flip
Default: destroy old resource first, then create replacement — causes downtime. create_before_destroy = true: provision the new resource first, then destroy the old one — zero-downtime replacement. Essential for load-balanced servers, SSL certificates, and Auto Scaling Groups.
default = destroy→create · cbd = create→destroy
Objective 4c
List the six variable precedence levels from lowest to highest.
Tap to flip
1. default (in variable block)
2. terraform.tfvars
3. *.auto.tfvars
4. -var-file flag
5. -var flag
6. TF_VAR_ environment variable (highest)
Default → tfvars → auto → file-flag → flag → ENV
Objective 4g
How does a precondition differ from a check block?
Tap to flip
precondition: inside a resource lifecycle block; runs before create/update; blocking — a failure aborts the plan. check block: standalone; runs after apply; non-blocking — a failure emits a warning only and does not fail the apply. postcondition is also blocking but runs after resource creation.
precondition = abort · check = warn
Objective 4h
A variable has sensitive = true. Is its value protected in Terraform state?
Tap to flip
No. sensitive = true only redacts the value in plan/apply terminal output (shows "(sensitive value)"). The value is still stored in plaintext in the Terraform state file. You must separately secure state — use encrypted remote backends, restrict access, and never commit state to VCS.
sensitive ≠ encrypted in state
Objective 4d
Compare list, set, and map: ordered? duplicates? key access?
Tap to flip
list(T): ordered, duplicates OK, index access (list[0]).
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.
list=ordered+dups · set=unique · map=key-value
Objective 4e
What does a splat expression do and what is the syntax?
Tap to flip
A splat expression collects a single attribute from all instances of a resource (or elements of a list) into a new list. Syntax: 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].
[*] = collect one attr from all instances

Study Advisor

Select the area where you need the most help. Get targeted review advice.

🧱
Resources & Data
resource vs data blocks, reference syntax, lifecycle ownership
📦
Variables & Outputs
precedence order, validation blocks, output sensitivity, complex types
🔗
Dependencies
implicit vs explicit, depends_on, lifecycle meta-arguments
Expressions & Functions
for expressions, splat, dynamic blocks, built-in functions
🔒
Sensitive & Lifecycle
sensitive flag, state exposure, ephemeral values, custom conditions

    Official Resources

    Primary sources for Terraform Associate 004 exam preparation.

    Practice Tests

    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

    Ready for the Terraform Associate 004 Exam?

    Test your full understanding with timed practice exams and detailed performance analytics.

    Start Free Practice Tests →