4 Expressions and Iteration - Terraform in Depth
4 Expressions and Iteration - Terraform in Depth
4 Expressions and Iteration - Terraform in Depth
So far in our quest to learn Terraform we’ve focused
on the objects of the Terraform language: the
resources, data sources, variables, and other blocks.
We’ve used those blocks to define and provision
infrastructure, and have even done a small amount
of validation using input validation blocks.
4.1.1 Research
Before you can make any changes to your module
you need to understand what it is you’re trying to
accomplish and what the best path forward is going
to be, and that always requires some research. You
are not expected to know everything. The world of
infrastructure has grown to the point where there
are tens of thousands of different systems out there
and not a single person who knows all of them. Even
for systems you’re familiar with, there may have
been changes, new features, or even deprecation of
features that mean your knowledge is out of date.
variable "tags" {
type = map(string) #A
description = "Key/Value pairs to pass to AWS as
default = {}
}
copy
tags = merge(var.tags, {
Name : "${var.name_prefix}-${count.index}" #D
})
}
copy
Listing 4.3 Using the count metaparameter on
our module.
module "my_instances" {
source = "github.com/tedivm/terraform_in_dept
count = 5 #A
subnet_id = var.subnet_id
}
copy
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"] #C
}
}
}
tags = merge(var.tags, {
Name : "${var.name_prefix}-${count.index}"
})
}
copy
copy
copy
4.1.6 Reviewing our Work
At this point we’ve evolved our module from
controlling a single simple instance to building a
series of dynamic resources. Our module can launch
multiple instances, supports connecting using
Systems Manager, and lets our users grant
permissions to any number of AWS systems.
Listing 4.8 Using our updated module.
module "my_instances" {
source = "github.com/tedivm/terraform_in_
num_instances = 5 #B
subnet_id = var.subnet_id
name_prefix = "example" #C
tags = {
"BillingGroup" = "mygroup" #D
}
}
copy
4.2.1 Math
+ Addition 5+4 9
- Subtraction 10-5 5
* Multiplication 5*5 25
/ Division 40/10 4
% Modulus 50/9 5
Listing 4.9
locals {
availability_zones = min(var.active_availability
needed_subnets = local.availability_zones *
}
copy
Other mathematical operations are available using
functions. We’ll talk about functions in the next
section of this chapter, but if you need to calculate
exponents, round numbers, or get absolute values
you can do that using functions.
4.2.2 Comparison
Operators are also used to compare values. This can
mean comparing the quantity of the value, such as
with the greater than and less than operators, or
whether the values are the same using the equality
operator.
copy
copy
module "nat_instance" {
source = "./modules/nat_instance"
count = var.use_nat_instance ? 1 : 0 #A
}
module "nat_gateway" {
source = "./modules/nat_gateway"
count = var.use_nat_instance ? 0 : 1 #B
}
output "nat_ip_address" { #C
value = var.use_nat_instance ? module.nat_instan
}
copy
4.2.5 Order
Just like algebra, Terraform operators have their own
order of operations. If an expression contains
multiple operators then they will be evaluated in this
order:
1. ! , - (when used to multiply by -1)
2. *, /, %
3. + , - (when used as subtraction)
4. > , >= , < , <=
5. == , !=
6. &&
7. ||
copy
Any comments on sectio…
4.3.1 Calling and Using Functions
Functions have a name, may take in arguments, and
always return values. A function has the format of
function_name(arg1, arg2, …), with the specific
arguments depending on the function themselves.
Functions aren’t required to have arguments, and
some even take in a dynamic number of arguments.
copy
copy
copy
copy
copy
copy
Even cooler, we can apply iteration in our templates.
If we have a map or a list we can iterate over all of the
values in our template.
copy
Listing 4.22 The template_file Resource Example
data "template_file" "main" { #A
template = file("${path.module}/example.tftpl")
vars = { #C
service_name = var.name
other_config = var.feature_enabled
}
}
copy
config_object = { #B
name = var.name
}
yaml_config = yamlencode(local.config_object) #C
json_config = jsonencode(local.config_object) #C
}
copy
copy
4.5.1 Regex
The regex function is useful when you absolutely
have to get the results out of a string, or otherwise
fail. With this function, if no matching string is
found it will raise an error.
#E
env_error = regex(var.name, "/^[A-Z]/")
}
copy
4.5.2 Regexall
copy
4.5.3 Replace
#C
flexible_swap = replace("Hello World", "/^[a-zA-
}
copy
copy
copy
output "feature_enabled" {
value = tobool(local.enable_flag) #A
}
copy
locals {
sensitive_uuid = sensitive(random_uuid.visible_u
}
copy
copy
These functions are great for normalizing data but
do not use them to hide real errors. If there’s a place
where errors are occurring you should always try to
eliminate the error itself rather than cover it up. One
area where these functions are great is validation
blocks, as they let you catch the errors and replace
them with better error messages. They can also be
useful inside of some output blocks. Outside of those
scenarios you should make sure to document your
reasoning for using it in a comment.
4.8.1 Count
The count metaparameter makes it possible for
Module and Resource blocks to create multiple
objects from a single block. It is a simpler method for
creating multiple objects. It takes in an integer and
creates that many items. If you pass in five it will
create five, if you pass in 0 as a value then it won’t
create any.
variable "num_instances" {
description = "How many instances to launch."
type = number
default = 0
}
copy
copy
4.8.2 For_each
The for_each metaparameter services a similar
function as count, except that it takes in an object or
list instead of an integer and gives developers access
to the each variable instead of the count variable.
This each variable is an object with two items, a
key and a value . For objects the key and value
map to the object’s own keys and values. When using
a list each element is passed to both each.key and
each.value .
copy
copy
variable "subnet_ids" {
type = list(string)
}
copy
4.9 For
The for statement allows developers to run a
transformation on every item in a group and then
return a new group with those transformations. It
can also be used to filter items out of the group.
As a very simple example, lets say you have a list of
strings and you want to add a prefix to each item in
the list.
copy
copy
copy
4.9.3 Filtering
The for statement can support filtering objects with
an optional if statement. When this statement isn’t
present then every item is included, but when it is
present then it only includes items if the statement
returns true.
4.9.5 Splat
locals { #A
config_id_for = [for x in var.object_config :
config_id_splat = var.object_config[*].id #B
}
copy
A common pattern is to use the splat operator to take
the output of a module or resource that uses “count”
or “for_each” to turn that output into a list. For
example, our instance example has an output for the
aws_instance_ip . We can use the splat operator to
get a list of aws_instance_ip values, which we can
then pass to a security group.
copy
Listing 4.45 Using splat to convert single items to
lists.
variable "security_group_id" {
type = string
}
copy
dynamic "ingress" { #A
for_each = var.security_group_rules #B
content {
description = ingress.description #C
from_port = ingress.from_port #C
to_port = ingress.to_port #C
protocol = ingress.protocol #C
cidr_blocks = ingress.cidr_blocks #C
}
}
ingress { #D
description = "HTTPS"
from_port = 443
to_port = 443
protocol = tcp
cidr_blocks = ["0.0.0.0/0"]
}
}
copy
dynamic "ingress" { #A
for_each = var.enable_public_https ? ["placeho
#B
content {
description = "Enable global access to port
from_port = 443 #C
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] #D
}
}
egress { #E
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
}
copy
In this chapter we learned how to use more of the
logical components of the Terraform language.
We’ve expanded our toolset to include the operators,
standard library of functions, and iteration; tools we
can use to make modules that are far more flexible.
In our next chapter we’re going to spend more time
focused on how Terraform takes our code and
converts it into a plan that it can then execute, which
in turn will help understand how to write more
efficient code and the types of problems we can run
into and how to avoid them.
4.11 Summary
Terraform has a variety of operators that can
be used to perform math, boolean operations,
and comparisons (such as greater
than/equals to).
There is a rich standard library of functions
built into Terraform, but those are the only
functions available as there is no way for
third parties to create or add new ones.
Functions in Terraform are primarily used to
transform data, not to take action.
Larger strings can be stored as either files or
templates, with templates having their own
small language that can be used to generate
complex strings from developer input.
Resources and Modules can have multiple
instances of resources created from the same
block through the use of count and
foreach .
Select
save
sitemap