This is the heart of Terraform configuration. The first half of Objective 4 covers the basics of resources, data sources, variables, outputs, and complex types. This second half dives into the more advanced features that make Terraform truly powerful and flexible.
-
Objective 4e: Write dynamic configuration using expressions and functions
-
Objective 4g: Validate configuration using custom conditions
-
Objective 4h: Understand best practices for managing sensitive data
These are the advanced configuration language features. They are not required for simple use cases, but they are essential for building robust, maintainable, and secure infrastructure as code. Mastering these will set you apart as a Terraform practitioner and prepare you for the more complex scenarios covered in later objectives.
Source: Perform dynamic operations with functions, Built-in Functions, Create dynamic expressions
Terraform expressions allow you to compute values dynamically rather than hardcoding them. The guide distinguishes:
- Simple values:
"hello",42,true - References:
var.name,aws_instance.web.id - Operators:
+,-,*,/,%,==,!=,>,<,&&,||,! - Conditionals:
condition ? true_val : false_val - For expressions:
[for s in var.list : upper(s)] - Splat expressions:
aws_instance.web[*].id - String templates:
"Hello, ${var.name}!" - Function calls:
upper("hello")
Note on types and errors:
- Terraform attempts safe type coercion where sensible (for example, converting numeric strings to numbers in some functions), but you should not rely on implicit conversions in complex expressions. Prefer explicit conversion functions like
tostring(),tonumber(), andtolist()when the type matters. - Use
can()to test whether an expression will succeed without causing a plan-time error. Example:can(regex("^v[0-9]+", var.version)). - Use
try(expr, fallback)to attempt expressions that may fail and provide a fallback value. This helps avoid runtime errors inforexpressions or optional attributes.
Best practice: keep expressions readable and predictable. If a single expression becomes too complex, compute intermediate values with
locals {}and give them descriptive names.
Syntax: condition ? value_if_true : value_if_false
resource "aws_instance" "web" {
instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"
}Key Points:
- Both result values must be of the same type (or convertible).
- The condition must evaluate to a boolean.
- Conditional expressions can be nested, but readability suffers.
For expressions transform lists, sets, tuples, maps, or objects into new collections.
List transformation:
[for s in var.names : upper(s)]
# Input: ["alice", "bob"] -> Output: ["ALICE", "BOB"]Map transformation:
{for k, v in var.tags : k => upper(v)}
# Input: {env = "dev"} -> Output: {env = "DEV"}Filtering with if:
[for s in var.names : upper(s) if length(s) > 3]Grouping with ... (ellipsis):
{for s in var.servers : s.name => s...}
# Creates a map keyed by name with the full object as valueThe splat operator [*] extracts a single attribute from all elements of a list.
resource "aws_instance" "web" {
count = 3
# ...
}
output "instance_ids" {
value = aws_instance.web[*].id
}For maps (from for_each), use values() first:
values(aws_instance.web)[*].idLegacy splat: aws_instance.web.*.id — still works but [*] is preferred.
Interpolation:
"Hello, ${var.name}!"Directives: (Terraform 1.6+)
"%{if condition}text%{else}other%{endif}"Heredoc syntax:
<<-EOT
This is a multi-line string.
Variables like ${var.name} are interpolated.
EOTThe offical study guide lists dozens of functions. The exam focuses on common ones and their use cases.
Numeric:
max(1, 5, 3)→5min(...)ceil(3.14)→4floor(3.14)→3
String:
upper("hello")→"HELLO"lower("HELLO")→"hello"join(", ", ["a", "b"])→"a, b"split(",", "a,b,c")→["a", "b", "c"]replace("hello world", "world", "Terraform")→"hello Terraform"substr("hello", 1, 3)→"ell"trimspace(" hi ")→"hi"regex("^[a-z]+", "abc123")→"abc"
Collection:
length([1,2,3])→3lookup(map, key, default)merge(map1, map2)concat(list1, list2)element(list, index)slice(list, start, end)keys(map)→ list of keysvalues(map)→ list of valuescontains(list, "value")→ booleanflatten(list_of_lists)setintersection(set1, set2)setunion(set1, set2)toset(list)→ removes duplicates
Filesystem:
file("path")→ returns file contents as stringtemplatefile("template.tpl", { var = value })→ renders templatefilebase64("path")→ base64 encoded content
Encoding:
jsonencode({ key = "value" })→{"key":"value"}jsondecode("...")yamlencode(...)yamldecode(...)base64encode(...)base64decode(...)
Date and Time:
timestamp()→ current UTC timestampformatdate("YYYY-MM-DD", timestamp())timeadd(timestamp(), "24h")
IP and CIDR:
cidrsubnet("10.0.0.0/16", 8, 1)→"10.0.1.0/24"cidrhost("10.0.0.0/24", 5)→"10.0.0.5"cidrnetmask("10.0.0.0/24")→"255.255.255.0"
Type Conversion:
tostring(value)tonumber(value)tobool(value)tolist(set)tomap(object)
- Beware of functions that return different types for different inputs (e.g.,
jsondecode()may produce maps, lists, or scalars). Explicitly validate or guard withcan()when needed. - Avoid heavy use of nested function calls in resource arguments; compute intermediate values in
locals {}for clarity and easier debugging. - Prefer
lookup(map, key, default)over direct indexing when keys may be absent to avoid plan-time errors. - Use
try()to provide fallbacks for optional or provider-specific attributes that may not exist in all contexts.
This is a critical exam topic. It reads a template file and interpolates variables into it.
Template file (user_data.tpl):
#!/bin/bash
echo "Server name: ${server_name}" >> /var/log/startup.log
echo "Environment: ${environment}" >> /var/log/startup.logUsage in Terraform:
resource "aws_instance" "web" {
user_data = templatefile("${path.module}/user_data.tpl", {
server_name = var.name
environment = var.env
})
}Key Points:
- Template variables use
${...}syntax (like Terraform, but separate context). - The second argument is a map of variable values.
- The result is a string that can be passed to resource arguments.
Some providers expose custom functions (Terraform 1.8+). Syntax:
provider::aws::arn_parse("arn:aws:s3:::mybucket")The exam may test recognition that functions can come from providers, not just built-in.
Directory: obj4e-lab
File: variables.tf
variable "servers" {
type = list(object({
name = string
size = string
}))
default = [
{ name = "web", size = "small" },
{ name = "db", size = "large" },
{ name = "cache", size = "medium" }
]
}File: template.tpl
Servers:
%{ for s in servers ~}
- ${s.name} (${s.size})
%{ endfor ~}
Generated at: ${timestamp}
File: main.tf
terraform {
required_providers {
local = { source = "hashicorp/local", version = "~> 2.5" }
}
}
locals {
large_servers = [for s in var.servers : s.name if s.size == "large"]
server_names = join(", ", [for s in var.servers : upper(s.name)])
}
resource "local_file" "report" {
filename = "${path.module}/report.txt"
content = templatefile("${path.module}/template.tpl", {
servers = var.servers
timestamp = formatdate("YYYY-MM-DD HH:mm", timestamp())
})
}
output "large_servers" {
value = local.large_servers
}
output "all_server_names_uppercase" {
value = local.server_names
}Steps:
-
terraform init -
terraform plan— Observe computed values. -
terraform apply -auto-approve -
Inspect
report.txtand outputs. -
Experiment with
terraform consoleto test functions interactively:terraform console > upper("test") "TEST" > max(1,5,3) 5 > slice(["a","b","c","d"], 1, 3) ["b", "c"] > cidrsubnet("10.0.0.0/16", 8, 2) "10.0.2.0/24"
-
True/False: The
templatefilefunction can only be used with files ending in.tpl. -
Multiple Choice: Which function would you use to combine two maps into one?
A. `concat`
B. `join`
C. `merge`
D. `union`
- Multiple Answer: Which of the following are valid Terraform expressions? (Select TWO)
A. `"Hello, ${var.name}"`
B. `[for x in var.list : x if x > 0]`
C. `if var.env == "prod" then "large" else "small"`
D. `var.tags["Name"]`
Answers
Answers & brief explanations
- False —
templatefilecan read any file, the extension does not matter. The.tplconvention is just for clarity. - C —
mergecombines maps.concatis for lists,joinis for strings, andunionis for sets. - A, B — A is a valid string interpolation, and B is a valid for expression with filtering. C is invalid syntax (should use
? :), and D is valid only ifvar.tagsis a map, but the question asks for expressions in general, so we cannot assume that.
Source: Create resource dependencies, Resource graph
Terraform automatically builds a dependency graph by analyzing resource references. The guide's Dependency Graph section details:
- Nodes: Resources, data sources, providers.
- Edges: Dependencies between nodes.
- Walk: Terraform traverses the graph in parallel, respecting dependencies.
Implicit Dependencies: Created when a resource references another resource's attribute.
resource "aws_vpc" "main" { ... }
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id # Implicit dependency
}Explicit Dependencies: Use depends_on when Terraform cannot infer the relationship.
resource "aws_s3_bucket" "data" { ... }
resource "aws_instance" "app" {
depends_on = [aws_s3_bucket.data] # Explicit
}The guide lists specific scenarios:
- When a resource needs to wait for a side effect that isn't captured in attributes (e.g., an IAM role propagation delay).
- When a resource depends on an entire module rather than a specific output.
- When you want to force ordering for application-level reasons.
- Modules do not implicitly share internal resources. If Module A creates something Module B needs, the common pattern is Module A exposes an output and the root (or caller) passes that output into Module B. Alternatively, you can set
depends_on = [module.module_a]in Module B's module block when the ordering must be enforced for the whole module. depends_onin a module block applies at the module-call site (root module) and ensures the entire child module waits for the referenced modules/resources.
Performance and maintenance caution:
- Each
depends_onedge can reduce concurrency; overusing it serializes applies and slows teams down. Use explicitdepends_ononly when the dependency is not otherwise represented by references or outputs.
Module-Level depends_on:
module "vpc" { ... }
module "app" {
depends_on = [module.vpc]
}Important Note: Overusing depends_on reduces parallelism and slows down applies. Only use when necessary.
The lifecycle block inside a resource controls how Terraform manages that resource's lifecycle.
resource "aws_instance" "web" {
# ... arguments ...
lifecycle {
create_before_destroy = true
prevent_destroy = false
ignore_changes = [tags, user_data]
replace_triggered_by = [aws_security_group.allow_ssh.id]
}
}Lifecycle Rules:
create_before_destroy(bool): When a resource must be replaced, create the new one before destroying the old one. Reduces downtime.
Example (avoid downtime for a database failover):
resource "aws_db_instance" "master" {
# ... args ...
lifecycle {
create_before_destroy = true
}
}Trade-offs: create_before_destroy may temporarily increase costs (two instances) and may not be possible when the provider or resource enforces unique names. In those cases, consider designing for immutable resources with blue/green patterns at the application layer.
prevent_destroy(bool): Iftrue, Terraform will error if a plan would destroy this resource. Protects critical resources.ignore_changes(list): Terraform will ignore differences in the listed attributes when planning updates.replace_triggered_by(list): Forces replacement of this resource if any of the referenced resources or attributes change.
replace_triggered_by is useful when a change in a dependent resource should cause a replacement of the consuming resource even if Terraform's default diff would not. Use it sparingly to keep replacements intentional.
Directory: obj4f-lab
File: main.tf
resource "random_pet" "first" {
length = 1
}
resource "random_pet" "second" {
length = 1
depends_on = [random_pet.first] # Explicit dependency
}
resource "local_file" "config" {
filename = "${path.module}/config.txt"
content = "first: ${random_pet.first.id}, second: ${random_pet.second.id}"
lifecycle {
ignore_changes = [content] # Changes to content won't trigger update
}
}
output "first_pet" {
value = random_pet.first.id
}
output "second_pet" {
value = random_pet.second.id
}Steps:
terraform init && terraform apply -auto-approve- Manually edit
config.txtcontent. Runterraform plan— no changes detected due toignore_changes. - Change the length of
random_pet.firstto2. Runterraform plan. Terraform will destroy and recreatefirst, thensecond(due todepends_on), butlocal_file.configwill not update because ofignore_changes. - Add
prevent_destroy = truetolocal_file.config. Try toterraform destroy— it will error.
- True/False:
depends_oncan be used inside adatablock. - Multiple Choice: Which lifecycle rule prevents accidental deletion of a resource?
A.
create_before_destroyB.prevent_destroyC.ignore_changesD.replace_triggered_by - Multiple Answer: When does Terraform automatically create an implicit dependency? (Select TWO)
A. When a resource references another resource's attribute.
B. When two resources share the same
providerblock. C. When a resource uses a data source that references a managed resource. D. When resources are defined in the same.tffile.
Answers
Answers & brief explanations
- True —
depends_oncan be used indatablocks to specify dependencies on resources or other data sources. - B —
prevent_destroywill cause Terraform to error if an operation would destroy the resource, providing a safeguard against accidental deletion. - A, C — An implicit dependency is created when a resource references another resource's attribute (A) or when a data source references a managed resource (C). Sharing the same provider block or being in the same file does not create dependencies.
Source: Validate your configuration, Validate modules with custom conditions, Use checks to validate infrastructure
Terraform offers multiple layers of validation:
| Type | Scope | When Evaluated | Blocks Operation? |
|---|---|---|---|
| Variable Validation | Input variables | During plan | Yes |
| Preconditions | Resources, data sources, outputs | Before creating/reading | Yes |
| Postconditions | Resources, data sources | After creating/reading | Yes |
| Checks | Any assertions | End of plan/apply | No (warning only) |
variable "instance_type" {
type = string
validation {
condition = can(regex("^t3\\.", var.instance_type))
error_message = "Only t3 instance types are allowed."
}
}Multiple validations per variable are allowed.
These are defined within a lifecycle block for resources and data sources, or directly in output blocks.
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
lifecycle {
# Check BEFORE creating the instance
precondition {
condition = data.aws_ami.ubuntu.architecture == "x86_64"
error_message = "AMI must be x86_64 architecture."
}
# Check AFTER creating the instance
postcondition {
condition = self.public_ip != null
error_message = "Instance must have a public IP."
}
}
}Output Precondition:
output "bucket_name" {
value = aws_s3_bucket.data.bucket
precondition {
condition = length(var.bucket_name) <= 63
error_message = "Bucket name must be 63 characters or less."
}
}Checks validate infrastructure without blocking operations. They run at the end of plan and apply.
check "website_health" {
data "http" "index" {
url = "https://${aws_instance.web.public_dns}"
}
assert {
condition = data.http.index.status_code == 200
error_message = "Website returned ${data.http.index.status_code}"
}
}Key Points:
checkblocks are not tied to a specific resource lifecycle.- They can contain their own
datasources, scoped only to the check. - Failures produce warnings, not errors.
Execution timing and CI guidance:
- Preconditions are evaluated before creating or reading the resource and will block
applywhen they fail. Postconditions run after the resource is created/read and can also block on failure. - Check blocks run at the end of
planandapplyand produce warnings (unless your CI treats warnings as failures). Use checks for observability and non-blocking alerts; use pre/postconditions for hard safety gates. - In CI pipelines, run
terraform planin a pre-merge job and treat failed preconditions as a gate. Run periodicterraform applyor the HCP Terraform checks runner for continuous validation against drift.
When enabled, HCP Terraform periodically evaluates check blocks and conditions in a workspace, alerting you to configuration drift or condition failures without requiring a new apply. This is covered in Objective 8.
Directory: obj4g-lab
File: variables.tf
variable "file_name" {
type = string
validation {
condition = can(regex("^[a-z]+\\.txt$", var.file_name))
error_message = "File name must be lowercase letters only and end with .txt"
}
}
variable "content_length" {
type = number
default = 10
}File: main.tf
resource "local_file" "data" {
filename = "${path.module}/${var.file_name}"
content = random_string.generator.result
lifecycle {
postcondition {
condition = length(self.content) == var.content_length
error_message = "Content length must be ${var.content_length}, got ${length(self.content)}"
}
}
}
resource "random_string" "generator" {
length = var.content_length
}
check "file_exists" {
data "local_file" "check" {
filename = local_file.data.filename
}
assert {
condition = fileexists(data.local_file.check.filename)
error_message = "File ${data.local_file.check.filename} was not created."
}
}Steps:
terraform init- Try
terraform plan -var="file_name=BAD.txt"— validation fails. - Use valid name:
terraform apply -var="file_name=good.txt" -auto-approve - Observe postcondition passes.
- Manually delete the file. Run
terraform plan— thecheckblock will still pass because it uses the data source defined within it (which reads the current state? Actually, the data source in the check will re-evaluate during plan and fail because file doesn't exist). This demonstrates checks catching drift. - Manually delete the file. Run
terraform plan— thecheckblock'sdatasource is evaluated during planning for the check and will fail its assertion because the file no longer exists. This demonstrates checks catching drift at plan/apply time.
-
True/False: A failed precondition will stop the
terraform applyfrom proceeding. -
Multiple Choice: Which validation type produces a warning rather than an error when it fails?
A. Variable validation
B. Precondition
C. Postcondition
D. Check block assertion
- Multiple Answer: Where can you define a precondition? (Select TWO)
A. In a `variable` block
B. In a `resource` lifecycle block
C. In a `data` lifecycle block
D. In a `provider` block
Answers
Answers & brief explanations
- True — A failed precondition will cause Terraform to error and stop the apply process.
- D — Check block assertions produce warnings when they fail, while variable validation, preconditions and postconditions produce errors.
- B, C — Preconditions can be defined in the lifecycle block of both
resourceanddatablocks. They cannot be defined invariableorproviderblocks.
Source: Protect sensitive input variables, Manage sensitive data in your configuration, Inject secrets into Terraform using the Vault provider
Sensitive data (API keys, passwords, tokens) must be handled carefully. Terraform stores all resource attributes in the state file in plaintext. Even if you mark a variable sensitive, the value is still in state.
variable "db_password" {
type = string
sensitive = true
}
output "db_password" {
value = aws_db_instance.master.password
sensitive = true
}Effects:
- Values are redacted in CLI output (
terraform plan,terraform apply). - They are not displayed in HCP Terraform UI.
- They remain in plaintext in the state file.
The ephemeral argument prevents a value from being stored in state at all.
variable "db_password" {
type = string
ephemeral = true
}Restrictions:
- Can only be used in specific contexts (provider configurations, provisioners, connection blocks).
- Cannot be referenced in normal resource arguments that persist to state.
Providers can define arguments with _wo suffix that are write-only. They are used to pass secrets without storing them.
resource "aws_db_instance" "master" {
password_wo = var.db_password
}The value is used for the API call but not stored in state.
| Method | State Protection | CLI Redaction | Notes |
|---|---|---|---|
sensitive = true |
No | Yes | Still in state plaintext |
ephemeral = true |
Yes (excluded) | N/A | Limited usage contexts |
| Write-only args | Yes (excluded) | N/A | Provider-specific |
| Remote state encryption | Yes (encrypted) | N/A | S3 SSE, HCP Terraform Vault |
| Vault Provider | Yes (dynamic) | Yes | Generates short-lived credentials |
- Store state in a remote backend that supports server-side encryption and access controls (e.g.,
S3 with SSE-KMSandDynamoDB locking,Azure Storagewith encryption, or HCP Terraform). Use least-privilege IAM roles for the backend and enable versioning on buckets to recover from accidental changes. - Never commit plaintext secret files or
.tfvarscontaining secrets into source control. Use environment variables, CI secret stores, or SOPS/age-encrypted files that are decrypted during CI runs. - In CI, avoid printing sensitive variables; use the CI provider's secret masking features and run
terraform planwith sensitive values provided via secure variables or a remote state provider. - When using Vault, prefer short-lived dynamic credentials via provider data sources rather than long-lived static tokens. Rotate secrets regularly and scope roles narrowly.
- Consider additional controls like encrypting state at rest, restricting who can run
terraform apply, and using policy checks (Sentinel/Opa/Run Tasks) to prevent risky changes.
The guide's Inject secrets into Terraform using the Vault provider tutorial describes a dynamic credential workflow.
provider "vault" {
address = "https://vault.example.com"
}
data "vault_aws_access_credentials" "creds" {
backend = "aws"
role = "terraform-role"
}
provider "aws" {
access_key = data.vault_aws_access_credentials.creds.access_key
secret_key = data.vault_aws_access_credentials.creds.secret_key
}Benefits:
- Credentials are short-lived (TTL).
- No long-lived keys stored in Terraform state.
- Vault manages the lifecycle of AWS IAM users dynamically.
Directory: obj4h-lab
File: variables.tf
variable "secret_message" {
type = string
sensitive = true
}File: main.tf
resource "local_file" "secret" {
filename = "${path.module}/secret.txt"
content = var.secret_message
}
output "file_content" {
value = local_file.secret.content
sensitive = true
}
output "file_path" {
value = local_file.secret.filename
}Steps:
terraform initterraform plan -var="secret_message=SuperSecret123"- Observe
secret_messageis(sensitive value)in plan.
- Observe
terraform apply -var="secret_message=SuperSecret123" -auto-approve- Output
file_contentis redacted.
- Output
- Run
terraform output file_content— value is displayed (because specific query bypasses redaction). - Inspect
terraform.tfstate— search forSuperSecret123. It's there in plaintext. - Run
terraform output -json—file_contentis exposed in JSON output.
Key Lesson: Sensitive is UI redaction only. State file security is paramount.
--
Light module-nesting joke:
Modules have feelings too — especially when nested. "Do you love me, parent module?" asks the child. The parent replies, "I reference you deeply, but please don't create a circular dependency!" 😄
-
True/False: Marking a variable as
sensitiveprevents its value from being written to the state file. -
Multiple Choice: Which Terraform feature ensures a value is never stored in state?
A. sensitive = true
B. ephemeral = true
C. sensitive on outputs
D. .gitignore
- Multiple Answer: What are best practices for managing sensitive data in Terraform? (Select TWO)
A. Store state files in encrypted remote backends.
B. Commit .tfvars files to version control.
C. Use dynamic credentials from Vault instead of static keys.
D. Hardcode secrets in locals blocks.
Show Answers
Answers & brief explanations
- False
sensitiveonly redacts values in CLI output, but they are still stored in plaintext in the state file. - (B)
ephemeral = trueprevents the value from being stored in state at all.sensitivedoes not prevent state storage, and.gitignoreis for ignoring files in version control, not state management. - (A, C) Storing state in encrypted remote backends and using dynamic credentials from Vault are best practices for managing sensitive data. Committing
.tfvarsfiles or hardcoding secrets inlocalsis not recommended.
Note down your answers, and check them against the provided solutions. (No peeping!)
Question 1 (True/False):
The expression [for s in ["a", "b"] : upper(s)] returns ["A", "B"].
Question 2 (Multiple Choice): Which function would you use to render a template file with variable substitution?
⬜ `file()`
⬜ `templatefile()`
⬜ `jsonencode()`
⬜ `format()`
Question 3 (Multiple Answer):
Which of the following are valid lifecycle rules? (Select THREE)
⬜ `create_before_destroy`
⬜ `prevent_apply`
⬜ `ignore_changes`
⬜ `replace_triggered_by`
⬜ `timeout`
Question 4 (True/False):
The depends_on meta-argument can be used inside a module block to ensure the module's resources are created after another module's resources.
Question 5 (Multiple Choice):
You have a map variable var.tags = { env = "dev", owner = "team" }. Which expression correctly retrieves the value of the env key?
⬜ `var.tags[0]`
⬜ `var.tags["env"]`
⬜ `lookup(var.tags, "env")`
⬜ Both B and C
Question 6 (Multiple Choice):
A precondition defined in a resource lifecycle block is evaluated:
⬜ After the resource is created.
⬜ Before the resource is created.
⬜ Only during `terraform validate`.
⬜ Only when using HCP Terraform.
Question 7 (True/False):
A failed check block assertion will prevent terraform apply from proceeding.
Question 8 (Multiple Choice): Which function converts a Terraform map into a JSON string?
⬜ `jsondecode()`
⬜ `jsonencode()`
⬜ `yamldecode()`
⬜ `tostring()`
Question 9 (Multiple Answer):
Which statements about the sensitive = true flag are correct? (Select TWO)
⬜ It encrypts the value in the state file.
⬜ It redacts the value from CLI output.
⬜ It prevents the value from being used in resource arguments.
⬜ It propagates to any expressions that reference the sensitive variable.
Question 10 (True/False):
The replace_triggered_by lifecycle rule can reference resources, data sources, or specific resource attributes.
Question 11 (Multiple Choice):
You need to wait 30 seconds after creating an aws_instance before creating an aws_route53_record. How should you implement this?
⬜ Use `depends_on` with a `time_sleep` resource.
⬜ Add `sleep 30` to the `user_data` script.
⬜ Use the `create_before_destroy` lifecycle rule.
⬜ Terraform automatically handles API propagation delays.
Question 12 (Multiple Choice):
What is the result of the expression merge({a=1}, {b=2})?
⬜ `{a=1, b=2}`
⬜ `[{a=1}, {b=2}]`
⬜ `["a", "b"]`
⬜ `[1, 2]`
Question 13 (True/False):
Postconditions are evaluated after a resource is created or updated, and a failed postcondition will roll back the resource creation.
Question 14 (Multiple Choice):
Which function would you use to find all elements in a list that match a condition?
⬜ `contains()`
⬜ A `for` expression with an `if` clause
⬜ `filter()`
⬜ `lookup()`
Question 15 (Multiple Answer):
Which of the following are valid arguments to the templatefile() function? (Select TWO)
⬜ The path to the template file.
⬜ A list of variables to interpolate.
⬜ A map of variable assignments.
⬜ A boolean flag for escaping.
Question 16 (True/False):
The terraform console command allows you to test expressions and functions interactively against the current state.
Question 17 (Multiple Choice):
You want to ignore manual changes to the tags attribute of an AWS instance so Terraform doesn't revert them. Which lifecycle rule should you use?
⬜ `prevent_destroy`
⬜ `create_before_destroy`
⬜ `ignore_changes`
⬜ `replace_triggered_by`
Question 18 (Multiple Answer):
Which of the following are valid ways to validate input variables? (Select TWO)
⬜ `validation` block within the `variable` block.
⬜ `precondition` block within the `variable` block.
⬜ Using the `sensitive = true` flag.
⬜ Setting a restrictive `type` constraint.
Question 19 (True/False):
Using the Vault provider to generate dynamic AWS credentials eliminates the need to store long-lived AWS credentials in Terraform state.
Question 20 (Multiple Choice):
What does the cidrsubnet("10.0.0.0/16", 8, 2) function return?
⬜ `10.0.0.2/24`
⬜ `10.0.2.0/24`
⬜ `10.2.0.0/16`
⬜ `10.0.0.2/32`
Answers
Answers & brief explanations
- True — The
forexpression iterates over the list and applies theupper()function to each element, resulting in["A", "B"]. templatefile()is the correct function for rendering a template file with variable substitution.create_before_destroy,ignore_changes, andreplace_triggered_byare valid lifecycle rules.prevent_applyandtimeoutare not valid lifecycle rules.- True —
depends_oncan be used in amoduleblock to ensure that the module's resources are created after another module's resources. - Both B and C — You can access the value using
var.tags["env"]orlookup(var.tags, "env"). - Before the resource is created — Preconditions are evaluated before creating or updating a resource.
- False — A failed
checkblock assertion produces a warning but does not preventterraform applyfrom proceeding. jsonencode()converts a Terraform map into a JSON string.- It redacts the value from CLI output, and it propagates to any expressions that reference the sensitive variable. The
sensitiveflag does not encrypt the value in the state file, nor does it prevent usage in resource arguments. - True — The
replace_triggered_bylifecycle rule can reference resources, data sources, or specific resource attributes to trigger replacement when they change. - Use
depends_onwith atime_sleepresource — This is a common pattern to handle API propagation delays. {a=1, b=2}— Themerge()function combines the two maps into one.- True — Postconditions are evaluated after a resource is created or updated, and if a postcondition fails, Terraform will roll back the resource creation or update.
- A
forexpression with anifclause — Terraform does not have a built-infilter()function, but you can achieve filtering with aforexpression. - The path to the template file, and a map of variable assignments — These are the valid arguments for
templatefile(). - True — The
terraform consolecommand allows you to test expressions and functions interactively against the current state. ignore_changes— This lifecycle rule tells Terraform to ignore changes to the specified attribute, allowing manual updates without Terraform reverting them.validationblock within thevariableblock, and setting a restrictivetypeconstraint — These are valid ways to validate input variables.preconditionblocks are not used withinvariableblocks, andsensitive = trueis for redaction, not validation.- True — Using the Vault provider to generate dynamic AWS credentials eliminates the need to store long-lived AWS credentials in Terraform state, as the credentials are generated dynamically and have a short TTL.
10.0.2.0/24— Thecidrsubnet()function creates a subnet within a given CIDR block, and the third argument specifies the subnet number.
Wow that was a lot of information! You should now have a solid understanding of Terraform's configuration language, including expressions, functions, dependencies, validation, and sensitive data management. This knowledge will be crucial for passing the Terraform Associate exam and for writing effective Terraform configurations in real-world projects.
Lets move on to Objective 5: Terraform Modules where we will explore the concept of modules, how to create and use them, and best practices for module development.