This is the Part 8 of the Terraform Associate Exam Cram. It covers the following Terraform Associate Certification exam objectives:
< Prev:
Objective 7 - Implement and maintain state
Next >:
Objective 9 - Understand HCP Terraform capabilities
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:
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:
-var
command line option..tfvars
or.tfvars.json
) files, specified with the
-var-file
command line option.terraform.tfvars
or
terraform.tfvars.json
or any files with names ending in .auto.tfvars
or
.auto.tfvars.json
).TF_VAR_<NAME>
).
Variable Definition Precedence
Terraform loads variables in the following order, with later sources taking precedence over (overwriting) earlier ones:
terraform.tfvars
file, if present.terraform.tfvars.json
file, if present.*.auto.tfvars
or *.auto.tfvars.json
files, processed in lexical order of their filenames.-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:
Outputs (output
)
Output values have several uses:
terraform apply
.
Outputs are only rendered when Terraform applies your plan. Running terraform plan
will not render outputs.terraform_remote_state
data source.
Output values exported by a module must be declared using output
blocks:
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:
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}"
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:
TF_VAR_db_password
).terraform.tfvars
(keep out of version control systems).-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:
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.
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>)
:
["a", "15", "true"]
.var.list[3]
.map(<TYPE>)
:
<KEY> = <VALUE>
pairs, separated by either a comma or a line break:
{ "foo": "bar", "bar": "baz" }
or { foo = "bar", bar = "baz" }
.var.map["key"]
, or the
dot-separated attribute notation: var.map.key
.set(<TYPE>)
:
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>, ... })
:
<KEY> = <VALUE>
pairs, separated by either a comma or a line break.var.object["key"]
,
or the dot-separated attribute notation: var.object.key
.tuple([<TYPE>, ... ])
:
["a", 15, true]
.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.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.
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.
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
.
for_each
value is a map, each.key
represents the current map key, and each.value
represents the corresponding value.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.
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
.
connection
block nested directly within a resource
affects all of that
resource's provisioners.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:
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.
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:
count
or for_each
count
meta-argument; individual objects can be accessed using
the square-bracket index notation, e.g., aws_instance.web[1]
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 resourceaws_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:
count
or for_each
count
meta-argument; individual objects can be accessed using
the square-bracket index notationfor_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:
count
nor for_each
setcount
meta-argument;
individual objects can be accessed using the square-bracket index notationfor_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
:
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]
.
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:
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:
A block has:
resource
in this example)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
).{
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:
<<EOT ... EOT
and <<-EOT ... EOT
${ ... }
%{if <BOOL>} ... %{else} ... %{endif}
and
%{for <NAME> in <COLLECTION>} ... %{endfor}
!
, -
(multiplication by -1),
*
, /
, %
, +
, -
(subtraction), >
,
>=
, <
, <=
, ==
, !=
, &&
, ||
.<CONDITION> ? <TRUE_VAL> : <FALSE_VAL>
).
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:
var.input_var
v
"Value: ${v}"
for each v
[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:
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:
min()
, max()
, ceil()
, parseint()
...format()
, join()
, trim()
, substr()
,
regex()
...length()
, contains()
, sort()
, merge()
,
values()
...base64encode()
, base64decode()
, jsondecode()
...file()
, fileexists()
...timestamp()
, timeadd()
...md5()
, bcrypt()
, sha256()
, uuid()
...cidrhost()
, cidrsubnet()
, cidrnetmask()
...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.
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
.
for_each
value is a map, <iterator_name>.key
represents the current map key,
and <iterator_name>.value
represents the corresponding value.<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.
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.output
block in a Terraform configuration?
lifecycle
meta-argument prevent_destroy
to true
in a resource block?
depends_on
meta-argument in a resource block?
terraform output
command?
locals
block in Terraform?
count
argument to create multiple instances of a resource?
create_before_destroy
to true
do in a resource block?
terraform graph
command?
terraform console
command?
terraform graph
command output by default?
null_resource.script
with a local-exec
provisioner. What command do you need to use first in order to re-run the script?
< Prev:
Objective 7 - Implement and maintain state
Next >:
Objective 9 - Understand HCP Terraform capabilities
More Terraform Tutorials:
Terraform Associate Exam Cram
Understanding Terraform Variable Precedence
Terraform Value Types Tutorial
Terraform count
Explained with Practical Examples
Terraform for_each
Tutorial with Practical Examples
Exploring Terraform dynamic
Blocks with GCP Examples
Working with External Data in Terraform
Terraform Modules FAQ