WintelGuy.com

Terraform Associate Exam Cram - Part 8

Read, generate, and modify configuration

This is the Part 8 of the Terraform Associate Exam Cram. It covers the following Terraform Associate Certification exam objectives:

8a. Demonstrate use of variables and outputs

Variables (variable)

Input variables let you customize aspects of Terraform modules without altering the module's own source code. This allows you to share modules across different Terraform configurations, making your module composable and reusable.

Each input variable accepted by a module must be declared using a variable block:

variable "region" { type = string default = "us-east-1" }

The label after the variable keyword is a name for the variable, which must be unique among all variables in the same module. This name is used to assign a value to the variable from outside and to reference the variable's value from within the module.

The name of a variable can be any valid identifier except the following: source, version, providers, count, for_each, lifecycle, depends_on, locals.

Optional arguments accepted by variable blocks:

  • default - A default value which, if provided, makes the variable optional.
  • type - This argument specifies what value types are accepted for the variable (type constraint). If no type constraint is set then a value of any type is accepted.
  • description - This specifies the input variable's description.
  • validation - A block to define validation rules, usually in addition to type constraints.
  • sensitive - Masks variable's value in Terraform output.
  • nullable - Specify if the variable can be null.

Variable Types

Type constraints are created from a mixture of type keywords and type constructors. The supported type keywords are:

  • string
  • number
  • bool

The type constructors allow you to specify complex types such as collection and structural types:

  • list(<TYPE>)
  • set(<TYPE>)
  • map(<TYPE>)
  • object({<ATTR NAME> = <TYPE>, ... })
  • tuple([<TYPE>, ...])

Using Input Variable Values

Within the module that declared a variable, its value can be accessed as var.<NAME>, where <NAME> matches the label given in the declaration block, for example: var.region.

The value assigned to a variable can only be accessed in expressions within the module where it was declared.

Assigning Values to Root Module Variables

When variables are declared in the root module of your configuration, they can be set in a number of ways:

  • In a HCP Terraform workspace.
  • Individually, with the -var command line option.
  • In variable definitions (.tfvars or.tfvars.json) files, specified with the -var-file command line option.
  • In automatically loaded variable definitions files (terraform.tfvars or terraform.tfvars.json or any files with names ending in .auto.tfvars or .auto.tfvars.json).
  • As environment variables (TF_VAR_<NAME>).

Variable Definition Precedence

Terraform loads variables in the following order, with later sources taking precedence over (overwriting) earlier ones:

  • Environment variables
  • The terraform.tfvars file, if present.
  • The terraform.tfvars.json file, if present.
  • Any *.auto.tfvars or *.auto.tfvars.json files, processed in lexical order of their filenames.
  • Any -var and -var-file options on the command line, in the order they are provided.

Assigning Values to Child Module Variables

Input variables values for a child module are assigned within the module block in the configuration of the parent module.

For example:

# vpc module variable "vpc_name" { type = string } variable "cidr_block" { type = string } # ...
# root module module "vpc" { source = "./modules/vpc" vpc_name = “example_vpc” cidr_block = "10.0.0.0/16" } # ...

Outputs (output)

Output values have several uses:

  • A child module can use outputs to expose a subset of its resource attributes to a parent module. Useful for chaining modules.
  • A root module can use outputs to print certain values in the CLI output after running terraform apply. Outputs are only rendered when Terraform applies your plan. Running terraform plan will not render outputs.
  • When using remote state, root module outputs can be accessed by other configurations via a terraform_remote_state data source.

Output values exported by a module must be declared using output blocks:

output "instance_ip" { value = aws_instance.web.public_ip }

The output block supports the following arguments:

  • value - The value Terraform returns for this output (Required).
  • description - A description of the output's purpose and how to use it.
  • sensitive - Specifies if Terraform hides this value in CLI output.
  • ephemeral - Specifies whether to prevent storing this value in state (Terraform v1.10 and later).
  • depends_on - A list of explicit dependencies for this output.
  • precondition - A condition to validate before computing the output or storing it in state. Additional attributes:
    • condition - Expression that must return true for Terraform to proceed with an operation.
    • error_message - Message to display if the condition evaluates to false.

Terraform stores output values in the configuration's state file.

In a parent module, outputs of child modules are available in expressions as module.<MODULE_NAME>.<OUTPUT_NAME>.

Use the terraform output [options] [OUTPUT_NAME] command to print the outputs. If OUTPUT_NAME is not specified, all outputs are printed.

Options:

  • -state=path - Path to the state file to read. Defaults to terraform.tfstate. Ignored when remote state is used.
  • -no-color - If specified, output won't contain any color.
  • -json - If specified, machine-readable output will be printed in JSON format.
  • -raw - For value types that can be automatically converted to a string, will print the raw string directly, rather than a human-oriented representation of the value.

Sensitive outputs

Terraform will redact outputs marked with sensitive = true when planning, applying, or destroying a configuration, or when querying all outputs with terraform output. Terraform will not redact sensitive outputs in other cases, such as when querying a specific output by name (terraform output <OUTPUT_NAME>), querying all outputs in JSON format (terraform output -json), or when using outputs from a child module in the root module.

Terraform stores all output values, including those marked as sensitive, as plain text in the state file.

Local Values (locals)

A local value assigns a name to a value or an expression and helps avoiding repetition of the same expression multiple times in a module.

Example:

locals { environment = "dev" vpc_name = "${local.environment}-vpc" } resource "google_compute_network" "vpc_network" { name = local.vpc_name auto_create_subnetworks = false }

Local values can be referred in expressions as local.<NAME>, e.g., local.environment, local.vpc_name.

Interpolation

A ${ ... } sequence is an interpolation, which evaluates the expression given between the brackets, converts the result to a string if necessary, and then inserts it into the final string, for example: name = "server-${var.id}"

Back to Top

8b. Describe secure secret injection best practice

Do not hard-code secrets in .tf files. Avoid placing secrets in Terraform state file wherever possible, and if placed there, take steps to secure the sate and reduce the risk by using remote backend, encryption at rest, and access control.

Preferred secret injection methods:

  • Environment variables (e.g., TF_VAR_db_password).
  • Secrets manager (HashiCorp Vault, AWS Secrets Manager, etc.).
  • Variable files, sach as terraform.tfvars (keep out of version control systems).
  • Pass via CLI (-var="db_password=...").

Vault Provider

HashiCorp Vault is a centralized tool for management of tokens, passwords, certificates, encryption keys, and other sensitive data.

The Vault provider allows Terraform to integrate with HashiCorp Vault to perform the following:

  • Generate short-lived dynamic credentials for various cloud providers including AWS, GCP, and Azure.
  • Read and write Vault secrets.
  • Utilize Terraform Cloud secrets engine to generate, manage and revoke credentials for HCP Terraform and Terraform Enterprise (Generating dynamic secrets for Terraform runs using Vault).

Interacting with Vault from Terraform causes any secrets to be persisted in both Terraform's state file and in any generated plan files. For any Terraform module that reads or writes Vault secrets, these files should be treated as sensitive and protected accordingly.

Note: Ephemeral variables and ephemeral resources were introduced in Terraform v1.10.

Back to Top

8c. Understand the use of collection and structural types

Primitive Types: string, number, bool.

Complex Types

A complex type is a type that groups multiple values into a single value. There are two categories of complex types: collection types (for grouping similar values), and structural types (for grouping potentially dissimilar values).

Collection Types

A collection type allows multiple values of one other type to be grouped together as a single value. The type of value within a collection is called its element type. All collection types must have an element type, which is provided as the argument to their constructor. All elements of a collection must always be of the same type.

  • list(<TYPE>):
    • an ordered sequence of values identified by consecutive whole numbers starting with zero.
    • can be represented by a pair of square brackets containing a comma-separated sequence of values: ["a", "15", "true"].
    • values can be accessed using the square-bracket index notation: var.list[3].
  • map(<TYPE>):
    • a collection of values where each is identified by a string label (key/value pairs).
    • can be represented by a pair of curly braces containing a series of <KEY> = <VALUE> pairs, separated by either a comma or a line break: { "foo": "bar", "bar": "baz" } or { foo = "bar", bar = "baz" }.
    • values can be accessed using the square-bracket index notation: var.map["key"], or the dot-separated attribute notation: var.map.key.
  • set(<TYPE>):
    • a collection of unique values that do not have any secondary identifiers or ordering.

Structural Types

A structural type allows multiple values of several distinct types to be grouped together as a single value. Structural types require a schema as an argument, to specify which types are allowed for which elements.

  • object({<ATTR NAME> = <TYPE>, ... }):
    • a collection of named attributes that each have their own type.
    • can be represented by a pair of curly braces containing a series of <KEY> = <VALUE> pairs, separated by either a comma or a line break.
    • values can be accessed using the square-bracket index notation: var.object["key"], or the dot-separated attribute notation: var.object.key.
  • tuple([<TYPE>, ... ]):
    • a sequence of elements identified by consecutive whole numbers starting with zero, where each element has its own type.
    • can be represented by a pair of square brackets containing a comma-separated sequence of values: ["a", 15, true].
    • values can be accessed using the square-bracket index notation: var.tuple[3].

Whenever possible, Terraform automatically converts between similar complex types. It also provides a number of type conversion functions:

  • tobool - converts its argument to a boolean value.
  • tolist - converts its argument to a list value.
  • tomap - converts its argument to a map value.
  • tonumber - converts its argument to a number value.
  • toset - converts its argument to a set value.
  • tostring - converts its argument to a string value.
  • type - returns the type of a given value.

Back to Top

8d. Create and differentiate resource and data configuration

Resources (resource)

Terraform creates and manages infrastructure with the help of resource blocks. All resources declared by resource blocks are known as managed resources.

resource "aws_instance" "web" { ami = "ami-12345" instance_type = "t2.micro" # ... }

A resource block declares a resource of a given type (aws_instance) with a given local name (web). The resource type and name together serve as an identifier for a given resource and must be unique within a module (aws_instance.web).

Within the block body (between { and }) are the configuration arguments for the resource itself. Most arguments in this section depend on the specific resource type.

There are also some meta-arguments that are defined by Terraform itself and apply across all resource types:

  • depends_on - specifies explicit dependencies, must be a list of references to other resources or child modules in the same calling module.
  • count - creates multiple resource instances based on a count.
  • for_each - creates multiple instances from a map or set of strings.
  • provider - selects a non-default provider configuration.
  • lifecycle - customizes resource lifecycle behavior. The arguments available within a lifecycle block are:
    • create_before_destroy - Terraform creates a replacement resource before destroying the current resource.
    • prevent_destroy - Terraform rejects operations to destroy the resource and returns an error.
    • ignore_changes - Specifies a list of resource attributes that Terraform ignores changes to. Otherwise, Terraform attempts to update the actual resource to match the configuration.
    • replace_triggered_by - Terraform replaces the resource when any of the referenced resources or specified attributes change.
    • precondition - Specifies a condition that Terraform evaluates before creating the resource.
    • postcondition - Specifies a condition that Terraform evaluates after creating the resource.
  • provisioner - performs extra actions on the local machine or on a remote machine after resource creation or before destruction. Built-in provisioners:
    • file - copies files or directories from the local machine to the newly created resource.
    • local-exec - invokes a local executable.
    • remote-exec - invokes a script on a remote resource.

count

The count meta-argument accepts a whole number and creates that many instances of a resource or module. When count is used, the block's address refers to a list of objects, with each object representing a distinct instance of a resource or module.

Terraform also provides a special attribute available within the block, count.index, which indicates the index number (starting from 0) of the instance currently being processed in the count loop. count.index can be used to modify the configuration of each instance, for example, by incorporating the index value into names, tags, etc.

resource "aws_instance" "web" { count = 2 # create two similar EC2 instances ami = "ami-a1234567" instance_type = "t2.micro" tags = { name = "web-${count.index + 1}" } }

In this example, the resource address aws_instance.web refers to a list containing two objects, which can be individually accessed as aws_instance.web[0] and aws_instance.web[1].

If count = 0, Terraform will not create any instances of the resource or module. By combining count with a conditional expression, you can control whether the resource is created. For example, count = var.create_bucket ? 1 : 0 instructs Terraform to create the bucket only when the create_bucket variable is true.

for_each

The for_each meta-argument accepts a map or a set of strings and creates an instance for each item in that map or set. When for_each is used, the block's address refers to a map of objects, with each object representing a distinct instance of a resource or module. Terraform links each map key (or set element) from the value provided to for_each with the specific instance processed in the for_each loop.

Terraform also provides two special attributes available within the block: each.key and each.value.

  • If for_each value is a map, each.key represents the current map key, and each.value represents the corresponding value.
  • If a set is used, each.key and each.value are identical, both representing the current set element.

The each.key and each.value attributes can be used to modify the configuration of each instance, for example, by incorporating the values into names, tags, or other attributes.

locals { web_servers = { primary = "t2.small" secondary = "t2.micro" } } resource "aws_instance" "web" { for_each = local.web_servers ami = "ami-a1234567" instance_type = each.value tags = { name = "web-${each.key}" } }

In this example, the resource address aws_instance.web refers to a map containing two objects, which can be individually accessed as aws_instance.web["primary"] and aws_instance.web["secondary"].

Note: A given resource or module block cannot use both count and for_each.

Provisioners (provisioner)

The file provisioner copies files or directories from the machine running Terraform to the newly created resource. It supports both ssh and winrm type connections. The file provisioner accepts the following arguments:

  • source - the source file or directory. Cannot be combined with content.
  • content - the direct content to copy to the destination. If the destination file does not exist, the content will be written to a file named tf-file-content created inside the target directory. Cannot be combined with source.
  • destination - the destination path to write to on the remote system.

The local-exec provisioner invokes a local executable (on the machine running Terraform) after a resource is created. It supports the following arguments:

  • command - the command to execute.
  • working_dir - specifies the working directory where command will be executed.
  • interpreter - a list of interpreter arguments used to execute the command. The first argument is the interpreter itself. The remaining arguments are appended prior to the command. If unspecified, sensible defaults will be chosen based on the system OS.
  • environment - a block of key value pairs representing the environment of the executed command.

The remote-exec provisioner invokes a script on a remote resource after it is created. It requires a connection and supports both ssh and winrm. The remote-exec provisioner accepts the following arguments (only one can be used per block):

  • inline - list of command strings. They are executed in the order they are provided.
  • script - a path to a local script that will be copied to the remote resource and then executed.
  • scripts - a list of paths to local scripts that will be copied to the remote resource and then executed. They are executed in the order they are provided.

If multiple provisioners are specified within a resource, they are executed in the order they're defined in the configuration file.

All provisioners support the when and on_failure meta-arguments.

Creation-time provisioners (the default behavior) run after the resource they are defined within is created and not during update or any other lifecycle. If a creation-time provisioner fails, the resource is marked as tainted. A tainted resource will be planned for destruction and recreation upon the next terraform apply.

Destroy-time provisioners (with when = destroy) run before the resource they are defined within is destroyed. If they fail, Terraform will error and rerun the provisioners again on the next terraform apply.

By default, provisioners that fail will also cause the Terraform apply itself to fail. The on_failure argument can change this behavior. The allowed values are:

  • continue - Ignore the error and continue with creation or destruction.
  • fail - Raise an error and stop applying (the default behavior). If this is a creation-time provisioner, mark the resource as tainted.

Expressions in provisioner blocks cannot refer to their parent resource by name. Instead, they can use the special self object which represents the provisioner's parent resource, and has all of that resource's attributes.

Most provisioners require access to the remote resource via SSH or WinRM and expect a connection block with details about how to connect. Connection blocks don't take a block label and can be nested within either a resource or a provisioner.

  • A connection block nested directly within a resource affects all of that resource's provisioners.
  • A connection block nested in a provisioner block only affects that provisioner and overrides any resource-level connection settings.

It is possible (but not recommended) to use third-party provisioners as plugins, by placing them in %APPDATA%\terraform.d\plugins, ~/.terraform.d/plugins, or the same directory where the Terraform binary is installed.

To run provisioners that are not directly associated with a specific resource, use a null_resource. Utilize its triggers argument and any meta-arguments to create necessary resource dependencies. triggers accepts a map of arbitrary strings that, when changed, will force the null_resource to be replaced, re-running any associated provisioners.

Data sources (data)

Data sources allow Terraform to use information defined outside of Terraform, defined by another separate Terraform configuration, or modified by functions. All data sources are essentially a read only subset of resources.

A data source is accessed via a special kind of resource known as a data resource, declared using a data block:

data "aws_ami" "ubuntu" { most_recent = true owners = ["099720109477"] # Canonical # ... }

A data block requests that Terraform read from a given data source (aws_ami) and export the result under the given local name (ubuntu).

The data source and name together serve as an identifier for a given resource and must be unique within a module (data.aws_ami.ubuntu). Exported attributes can be accessed using the following form: data.<TYPE>.<NAME>.<ATTRIBUTE>.

Within the block body (between { and }) is configuration for the data instance. Most arguments in this section depend on the specific data source.

data blocks accept the same meta-arguments as resource blocks with the exception of the lifecycle configuration block.

Back to Top

8e. Use resource addressing and resource parameters to connect resources together

A resource address is a string that identifies zero or more resource instances in overall Terraform configuration.

Input Variables

var.<NAME> is the value of the input variable of the given name.

Local Values

local.<NAME> is the value of the local value of the given name.

Resources

<RESOURCE_TYPE>.<NAME> represents a managed resource of the given type and name. Its value is:

  • an object representing a single instance, if the resource does not use count or for_each
  • a list of objects, if the resource has the count meta-argument; individual objects can be accessed using the square-bracket index notation, e.g., aws_instance.web[1]
  • a map of objects, if the resource has the for_each meta-argument; individual objects can be accessed using the square-bracket index notation, for example: aws_instance.web["main"]

The resource's attributes are elements of the object and can be accessed using dot or square bracket notation. For example:

  • aws_instance.web.id - accessing id of a single-instance resource
  • aws_instance.web[1].id - resource using count, accessing id of the second instance (index starts at 0)
  • aws_instance.web["main"].id - resource using for_each, accessing id of the instance with key "main"

Data Sources

data.<DATA_TYPE>.<NAME> is an object representing a data resource of the given data source type and name. Its value is:

  • an object representing a single instance, if the date resource does not use count or for_each
  • a list of objects, if the date resource has the count meta-argument; individual objects can be accessed using the square-bracket index notation
  • a map of objects, if the date resource has the for_each meta-argument; individual objects can be accessed using the square-bracket index notation

The data resource's attributes are elements of the object and can be accessed using dot-separated attribute notation, e.g., data.<DATA_TYPE>.<NAME>.<ATTRIBUTE_NAME>.

Child Module Outputs

module.<MODULE_NAME> is a value representing the results of a module block. Its value is:

  • an object representing a single module instance, if the corresponding module block does not have neither count nor for_each set
  • a list of objects, each representing one module instance, if the resource has the count meta-argument; individual objects can be accessed using the square-bracket index notation
  • a map of objects, each representing one module instance, if the resource has the for_each meta-argument; individual objects can be accessed using the square-bracket index notation

The module's outputs are elements of the object (one attribute for each output value defined in the module) and can be accessed using dot-separated attribute notation, e.g., module.<MODULE_NAME>.<OUTPUT_NAME>.

Connecting Resources

Constructs like resources and module blocks often use references to other resources and variables. Terraform analyzes these expressions to automatically builds dependencies between objects. An expression in a resource argument that refers to another managed resource creates an implicit dependency between the two resources. In following example, aws_eip.example depends on aws_instance.web:

resource "aws_eip" "example" { instance = aws_instance.web.id # ... }

If needed, explicit dependencies can be created using the depends_on argument which can be added to any resource or module block and accepts a list of resources to create explicit dependencies for: depends_on = [aws_s3_bucket.example, aws_instance.example].

Back to Top

8f. Use HCL and Terraform functions to write configuration

Terraform's configuration language is based on a more general language called HashiCorp Configuration Language (HCL). The main purpose of the Terraform language is to create declarative configurations that represent infrastructure objects.

The syntax of the Terraform language consists of only a few basic elements:

  • Blocks are containers for other content and usually represent the configuration of some kind of object, like a resource. Blocks have a block type, can have zero or more labels, and have a body that contains any number of arguments and nested blocks. Most of Terraform's features are controlled by top-level blocks in a configuration file.
  • Arguments assign a value to a name. They appear within blocks.
  • Expressions represent a value, either literally or by referencing and combining other values. They appear as values for arguments, or within other expressions.

While the configuration language is not a programming language, you can use several built-in functions to perform operations dynamically.

A Terraform configuration is a complete document that tells Terraform how to manage a given collection of infrastructure. A configuration can consist of multiple files and directories.

The ordering of blocks and the files they are organized into are generally not significant. Terraform only considers implicit and explicit relationships (dependencies) between resources when determining an order of operations.

Terraform expects native syntax for files named with a .tf suffix, and JSON syntax for files named with a .tf.json suffix.

Arguments

An argument assigns a value to a particular name: image_id = "abc123". The identifier before the equals sign is the argument name, and the expression after the equals sign is the argument's value.

Blocks

A block is a container for other content:

resource "aws_instance" "example" { ami = "abc123" network_interface { # ... } # ... }

A block has:

  • a type (resource in this example)
  • labels (aws_instance and example) - The number of labels is determined by the block type. The resource block type expects two labels: the resource type (aws_instance) and an arbitrary name (example).
  • a body (delimited by the { and } characters) - Within the block body, further arguments and blocks may be nested, creating a hierarchy of blocks and their associated arguments.

Identifiers

Argument names, block type names, and the names of most Terraform-specific constructs like resources, input variables, etc. are all identifiers. Identifiers can contain letters, digits, underscores (_), and hyphens (-). The first character of an identifier must not be a digit, to avoid ambiguity with literal numbers.

Built-in Values

  • path.module - the filesystem path of the module.
  • path.root - the filesystem path of the root module.
  • path.cwd - the filesystem path of the original working directory from where you ran Terraform before applying any -chdir argument.
  • terraform.workspace - the name of the currently selected workspace.

Expressions

Expressions refer to or compute values within a configuration, including references to variables or to data exported by resources, arithmetic operations, conditional evaluations, function calls, etc.

Please refer to the official Terraform documentation for more details about Terraform's expression syntax:

  • Types and Values - describes Terraform data types and value syntax.
  • Strings and Templates - explains string syntax & templates:
    • heredoc - <<EOT ... EOT and <<-EOT ... EOT
    • interpolation - ${ ... }
    • directives - %{if <BOOL>} ... %{else} ... %{endif} and %{for <NAME> in <COLLECTION>} ... %{endfor}
  • References to Values - shows how to refer to variables and resource attributes.
  • Operators - describes available arithmetic, comparison, and logical operators: !, - (multiplication by -1), *, /, %, +, - (subtraction), >, >=, <, <=, ==, !=, &&, ||.
  • Function Calls - explains syntax for calling built-in functions.
  • Conditional Expressions - describes expressions that return values based on a condition (<CONDITION> ? <TRUE_VAL> : <FALSE_VAL>).
  • For Expressions - explains how to transform complex data types using iteration.
  • Splat Expressions - shows how to extract simpler collections from more complicated expressions.
  • Dynamic Blocks - describes creating multiple repeatable nested blocks within a resource or other construct.
  • Validate your configuration - explains how to verify variable conditions, check blocks, and resource preconditions and postconditions.
  • Type Constraints - defines syntax for specifying accepted variable types.
  • Version Constraints - explains syntax for restricting allowed software versions.

for Expressions

A for expression performs transformation between complex types. It accepts any collection or structural type (a list, a set, a tuple, a map, or an object) as an input and uses an arbitrary user-defined expression to generate either a tuple (with [ ]) or an object (with { }) as an output.

The following examples show various for expression options, determined by the input and output types:

[for v in var.input_var : "Value: ${v}"] - This for expression perform the following:

  • iterates over each element of var.input_var
  • assigns the value of each respective element to the temporary symbol v
  • evaluates the expression "Value: ${v}" for each v
  • returns a tuple value with evaluation results as elements

[for k, v in var.input_var : "Key: ${k}, Value: ${v}"] - This example illustrates how to use for with two temporary symbols iterating over a map or an object. The temporary symbols k and v are set to the key/attribute and the value of each element of var.input_var, respectively. The for expression then evaluates "Key: ${k}, Value: ${v}" for each k and v pair and returns a tuple value with evaluation results as elements.

[for i, v in var.input_var : "Index: ${i}, Value: ${v}"] - This for expression is similar to the one above but it iterates over a list or a tuple. It sets i to the index and v to the value of each respective element. It then evaluates the expression "Index: ${i}, Value: ${v}" and includes the results into the output tuple.

{for i, v in var.input_var : "key-${i}" => "Value: ${v}"} - This for expression returns an object. It uses two temporary symbols i and v and evaluates two expressions separated by =>. "key-${i}" defines the attributes of the output object, and "Value: ${v}" - the corresponding values.

[for v in var.regions : v if can(regex("^us", v))] - A for expression can include an optional if clause to filter elements from the source collection. In this example, Terraform iterates over the list of regions (var.regions) and returns only those whose names start with "us" as determined by the if can(regex("^us", v)) clause.

{for k, v in var.input_var : v => k...} - This for expression operates in grouping mode activated by adding ... after the value expression. In this example, for returns an object where each attribute contains a tuple value, with one or more elements each. Note: Grouping cannot be used when building a tuple.

Splat Expressions

A splat expression is identified by the special symbol [*] and can be used only with lists, sets, and tuples. It iterates over all of the element of the object given to left of [*] and returns a list or a tuple containing the respective values of the attribute given on the right of [*]. For example:

variable "list" { type = list(object({ id : string })) default = [{ id = "a" }, { id = "b" }] } locals { list_splat = var.list[*].id }
$ terraform console > var.list tolist([ { "id" = "a" }, { "id" = "b" }, ]) > local.list_splat tolist([ "a", "b", ]) >

The splat expression does not work with maps or objects, use for expressions in such cases.

Functions

The Terraform language has a number of built-in functions that can be used in expressions to transform and combine values:

  • Numeric Functions - min(), max(), ceil(), parseint() ...
  • String Functions - format(), join(), trim(), substr(), regex() ...
  • Collections Functions - length(), contains(), sort(), merge(), values() ...
  • Encoding Functions - base64encode(), base64decode(), jsondecode() ...
  • Filesystem Functions - file(), fileexists() ...
  • Date and Time Functions - timestamp(), timeadd() ...
  • Hash and Crypto Functions - md5(), bcrypt(), sha256(), uuid() ...
  • IP Network Functions - cidrhost(), cidrsubnet(), cidrnetmask() ...
  • Type Conversion Functions - can(), tostring(), try(), tolist() ...

Please refer to the official Terraform documentation for more details about Terraform's functions.

Note: The Terraform language does not support user-defined functions, and so only the functions built in to the language are available for use.

Dynamic Blocks

A dynamic block lets you programmatically generate multiple repeatable nested blocks of the same type, using input data structured as a set or map. Dynamic blocks can be added to resource, data, provider, and provisioner blocks.

resource "resource_type" "resource_name" { # ... dynamic "block_name" { for_each = <collection> iterator = <iterator_name> # optional, defaults to block_name content { # defines the body of each generated block # block content using iterator_name or block_name } } }

The iterator argument (optional) sets the name of a temporary variable that represents the current element of the value assigned to for_each. If omitted, the name of the variable defaults to the label (block_name) of the dynamic block.

Terraform also provides two special attributes available within the block: <iterator_name>.key and <iterator_name>.value.

  • If for_each value is a map, <iterator_name>.key represents the current map key, and <iterator_name>.value represents the corresponding value.
  • If a set is used, <iterator_name>.key and <iterator_name>.value are identical, both representing the current set element.

The <iterator_name>.key and <iterator_name>.value attributes can be used to modify the configuration of each nested block.

Back to Top

8g. Describe built-in dependency management (order of execution based)

Terraform automatically builds a resource dependency graph from the configuration. It uses this graph to determine the correct order for creating resources, rather than relying on the order in which they appear in the configuration files. Terraform creates resources in parallel when no dependency exists.

Explicit dependency between resources can be added with the depends_on meta-argument accepted by any resource or module block. depends_on must be assigned a list of references to other resources or child modules in the same calling module, for example: depends_on = [aws_s3_bucket.example, aws_instance.example].

The terraform graph [options] command produces a representation of the dependency graph between different objects in the current configuration or execution plan. The output is in the DOT format, which can be used by GraphViz to generate charts.

Options:

  • -plan=tfplan - Render graph using the specified plan file instead of the configuration in the current directory.
  • -draw-cycles - Highlight any cycles in the graph with colored edges. This helps when diagnosing cycle errors.
  • -type=plan - Type of graph to output. Can be: plan, plan-refresh-only, plan-destroy, or apply. By default, Terraform chooses plan, or apply based on the -plan=... option.
  • -module-depth=n - (deprecated) In prior versions of Terraform, specified the depth of modules to show in the output.

Back to Top

Practice Questions

Which file extension is commonly used for Terraform configuration files?
How can you provide values for variables in Terraform?
What is the purpose of the output block in a Terraform configuration?
Which block is used to specify the configuration of a resource in Terraform?
In Terraform what is the effect of setting the lifecycle meta-argument prevent_destroy to true in a resource block?
Which syntax allows you to define a map variable in Terraform?
What is the purpose of the depends_on meta-argument in a resource block?
Which is the correct syntax for defining a count on a resource in Terraform?
How can you ignore changes to a specific resource attribute?
What is the primary purpose of the terraform output command?
In Terraform, which function is used to concatenate strings?
How can you provide different values for variables in multiple environments (e.g., Dev, Prod)?
What is the purpose of the locals block in Terraform?
What is the correct way to write a conditional expression in Terraform?
What is the correct way to define an output that is sensitive in Terraform?
Does HashiCorp Configuration Language (HCL) support user-defined functions?
Which Terraform block is specifically designed to dynamically construct a collection of nested configuration blocks?
Which block in Terraform is used to define authentication details for provisioners that connect to remote resources?
How does Terraform determine dependencies between resources?
How can you specify that a resource should only be created if a condition is true?
How do you define a list variable in Terraform?
Which block type is used to define output values in Terraform?
In Terraform how can you use a count argument to create multiple instances of a resource?
How do you define a boolean variable in Terraform?
Which block type is used to define a Terraform data source?
What does setting create_before_destroy to true do in a resource block?
Which block in a terraform configuration allows specifying conditions for resource creation or destruction?
What is the purpose of the terraform graph command?
Which Terraform features and capabilities can be used to manage different environments?
How can you reference an attribute from a different resource in Terraform?
Which provisioner is used to execute commands on the machine running Terraform?
How can you enforce a specific order of resource creation in Terraform?
Which attribute can be used to specify that a resource should be replaced when a certain condition is met?
Which command shows the outputs of your Terraform configuration?
How do you define an output value in a Terraform configuration?
What is the purpose of the terraform console command?
What does the terraform graph command output by default?
How do you specify that a particular resource should only be created if a condition is met?
Your Terraform configuration contains a resource named null_resource.script with a local-exec provisioner. What command do you need to use first in order to re-run the script?
Which provisioner invokes a process on the resource created by Terraform?
Do Terraform variable and output descriptions get stored in the state file?
Should you store sensitive or secret data (such as passwords, API keys, or private keys) in the same version control repository as your Terraform configuration files?

Back to Top