Terraform functions deep dive
Uderstand how to use Terraform functions to write better code.
DevOps Engineer�she/her��@holmes_shin
Yen Trinh
Terraform functions overview.
01
What is Terraform functions?
Terraform: Declarative but powerful.
> 100 built-in functions.
Expressions that transform inputs into outputs.
Not support for user-defined or external libraries imported functions.
Multiple types: numeric, string, encoding, collections, etc.
What is Terraform functions?
The general syntax for function calls is a function name followed by comma-separated arguments in parentheses. For example: max(5, 12, 9).
FUNCTION NAME(ARGUMENT 1, ARGUMENT 2)
What is Terraform functions?
terraform console
You can use “terraform console” to have fun with Terraform functions…
Why use Terraform functions?
Deep dive in some core functions.
02
String functions.
Functions to perform string manipulation.
There are plenty of string functions in Terraform: chomp, format, endswith, formatlist, indent, lower, regex, regexall, replace, split, startswith, strrev, substr, title, trim, trimprefix, trimsuffix, trimspace, upper
String functions.
Functions that we focus today.
join
format()
Produces a string by formatting a number of other values according to a specification string.
> format("There are %d lights.", 4)
There are 4 lights.
> format("Region name: %s!", local.regions[1])
"Region name: us-east-2!"
format("There are %d lights", 4)
CODE EDITOR
split()
# split up the string
output "ip_addresses" { # "192.168.0.1,192.168.0.2,192.168.0.3"
value = split(",",demo.mydempapp.outbound_ip_addresses)
}
# Terraform Plan Output
Changes to Outputs:
+ ipaddreses = [
+ "192.168.0.1",
+ "192.168.0.2",
+ "192.168.0.3",
]
CODE EDITOR
join()
Produces a string by concatenating elements of the specified list of strings with the specified separator..
# Define a List in a variable
variable "example-list" {
default = ["one","two","three"]
}
# Returns "one-two-three"
output "join-example" {
value = "${join("-",var.example-list)}"
}
format("There are %d lights", 4)
CODE EDITOR
substr()
# Returns hello
locals {
message = "hello world"
substring = "${substr(local.message, 0, 5)}"
}
# Returns world
"${substr(local.message, -5, -1)}"
CODE EDITOR
replace()
Searches a given string for another given substring, and replaces each occurrence with a given replacement string.
input = [ "http://medium.com/", "http://www.facebook.com/" ]
output = [
for item in var.input:
replace(item, "http://", "https://")
]
# The contents of the output variable are going to be:
output = [ "https://medium.com/", "https://www.facebook.com/" ]
TERMINAL
regex()
Produces a string by concatenating elements of the specified list of strings with the specified separator.
# For the application_name value only a-z, A-Z and 0-9 are allowed:
condition = can(regex("^[0-9A-Za-z]+$", var.application_name))
# To extract all text inside parentheses:
regex("[(]([^()]+)[)]", "Mark Johnson (mark@johnson.com)")
format("There are %d lights", 4)
CODE EDITOR
Numeric functions.
Functions to perform mathematical operations on numeric values.
Available functions: abs, ceil, floor, log, max, min, parseint, pow, signum
Numeric functions.
variable "num" {
type = set(number)
default = [250, 10, 11, 5]
description = "A set of numbers"
}
> max(var.num...)
250
> ceil(10.1)
11
> ceil(10.9)
11
> floor(10.9)
10
TERMINAL
Collection functions.
Functions to manipulate collections – arrays, objects, lists, and maps.
Availables functions: alltrue, anytrue, chunklist, coalesce, coalescelist, compact, concat, contains, distinct, element, flatten, index, keys, values, length, list, lookup, map, matchkeys, merge, one, range, reverse, setintersection, setproduct, setsubtract, setunion, slice, sort, sum, transpose, zipmap.
coalesce()
Return the first that isn't null or an empty string, error if all arguments are empty/null.
// Does the first param have a value? Return it.
// Otherwise, does the second param have a value? Return it.
// Otherwise, does the nth param have a value? Return it
> coalesce(1, 2, 3)
1
> coalesce(null, 2, 3)
2
> coalesce(null, null, 3)
3
> coalesce(null, null, null)
Error: Error in function call
CODE EDITOR
concat()
conditional_append = concat(var.set_a ? ["A"]:[], var.set_b ? ["B"]:[])
# Setting one of conditions to true:
set_a = true
set_b = false
=> conditional_append = ["A"]
# Setting both of conditions to true:
set_a = true
set_b = true
=> conditional_append = ["A“, "B"]
"B"
CODE EDITOR
> element(["a", "b", "c"], 1)
"b“
> element(["a", "b", "c"], length(["a", "b", "c"])-1)
"c“
# Example in .tf file:
resource "aws_instance" "example" {
subnet_id = element(var.subnet_ids, 0)
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
element()
Retrieves a single element from a list. This function produces an error if used with an empty list.
TERMINAL
keys() and values()
keys: returns a list containing the keys from map; values: returns a list containing the values of the elements in map.
locals {
key_value_map = {
"key1" : "value1",
"key2" : "value2"
}
key_list = keys(local.key_value_map)
value_list = values(local.key_value_map)
}
output "key_list" {
value = local.key_list
}
output "value_list" {
value = local.value_list
}
CODE EDITOR
locals {
unflatten_list = [[1, 2, 3], [4, 5], [6]]
flatten_list = flatten(local.unflatten_list)
}
# Returns [1, 2, 3, 4, 5, 6]
output "flatten_list" {
value = local.flatten_list
}
flatten()
Flatten a list of lists into a single list, useful when reducing a nested data structure into a flat one in for_each and dynamic block.
TERMINAL
lookup()
Retrieves a value from a map using its key. If the value is not found, return the default value instead.
locals {
a_map = {
"key1" : "value1",
"key2" : "value2"
}
lookup_in_a_map = lookup(local.a_map, "key1", "test")
}
# Returns lookup_in_a_map = "key1“
output "lookup_in_a_map" {
value = local.lookup_in_a_map
}
CODE EDITOR
locals {
b_map = {
"key1" : "value1",
"key2" : "value2"
}
c_map = {
"key3" : "value3",
"key4" : "value4"
}
final_map = merge(local.b_map, local.c_map)
}
merge()
Takes one or more maps and returns a single map that contains all of the elements from the input maps.
TERMINAL
range()
Creates a range of numbers:
locals {
range_one_arg = range(3)
range_two_args = range(1, 3)
range_three_args = range(1, 13, 3)
}
output "ranges" {
value = format("Range one arg: %v. Range two args: %v. Range three args: %v", local.range_one_arg, local.range_two_args, local.range_three_args)
}
# The output for this code would be:
range = "Range one arg: [0, 1, 2]. Range two args: [1, 2]. Range three args: [1, 4, 7, 10]"
CODE EDITOR
slice()
locals {
slice_list = slice([1, 2, 3, 4], 2, 4)
}
# The output would be slice_list = [3].
output "slice_list" {
value = local.slice_list
}
"B"
CODE EDITOR
locals {
key_zip = ["a", "b", "c"]
values_zip = [1, 2, 3]
zip_map = zipmap(local.key_zip, local.values_zip)
}
output "zip_map" {
value = local.zip_map
}
# This code will return:
zip_map = {
"a" = 1
"b" = 2
"c" = 3
}
zipmap()
Constructs a map from a list of keys and a list of values.
TERMINAL
Type conversion functions.
Functions to convert data types.
Available functions: can, nonsensitive, sensitive, tobool, tolist, tomap, tonumber, toset, tostring, try, type
Type conversion functions.
ToType Functions.
# The validation in this code will give you an error: “This is not a number: 1”:
variable "a" {
type = any
validation {
condition = can(tonumber(var.a))
error_message = format("This is not a number: %v", var.a)
}
default = "1"
}
can()
Evaluates an expression and returns a boolean indicating if there is a problem with the expression, useful for validating variables.
TERMINAL
try()
Use a value if it is usable but fall back to another value if the first one is unusable.
# The output of this code will be “fallback”, as the expression local.map_var.test2 is unusable:
locals {
map_var = {
test = "this"
}
try1 = try(local.map_var.test2, "fallback")
}
output "try1" {
value = local.try1
}
CODE EDITOR
Date and time functions.
Functions for date and time manipulation.
Available functions: formatdate, timeadd, timecmp, timestamp.
timestamp()
locals {
current_time = timestamp()
}
output "current_time" {
value = local.current_time
}
# Apply result:
current_time = "2023-04-18T13:35:50Z"
"B"
CODE EDITOR
timeadd()
Adds a duration to a timestamp, returning a new timestamp.
locals {
today = timestamp()
tomorrow = timeadd(local.today, "24h")
twelve_hours_plus = timeadd(local.today, "12h")
ten_minutes_plus = timeadd(local.today, "10m")
}
output "today" {
value = local.today
}
output "now_plus_ten_minutes" {
value = local.ten_minutes_plus
}
# now_plus_ten_minutes = "2023-04-18T13:51:18Z"
# today = "2023-04-18T13:41:18Z"
TERMINAL
locals {
timestamp = timestamp()
day = formatdate("YYYY-MM-DD", local.timestamp)
time = formatdate("hh:mm:ss", local.timestamp)
day_name = formatdate("EEEE", local.timestamp)
}
output "day" {
value = local.day # day = "2023-04-18"
}
output "time" {
value = local.time # time = "13:47:22"
}
output "day_name" {
value = local.day_name # day_name = "Tuesday"
}
formatdate()
Converts a timestamp into a different time format..
TERMINAL
Encoding functions.
Encoding and decoding functions that deal with various formats – base64, text, JSON, YAML, etc.
Available functions: base64decode, base64encode, base64gzip, csvdecode, jsondecode, jsonencode, textdecodebase64, textencodebase64, urlencode, yamldecode, yamlencode.
jsondecode()
Interprets a string as json.
locals {
a_jsondecode = jsondecode("{\"hello\": \"world\"}")
}
output "a_jsondecode" {
value = local.a_jsondecode
}
# This will return:
jsondecode = {
"hello" = "world"
}
TERMINAL
locals {
a_jsonencode = jsonencode({ "hello" = "world" })
}
output "a_jsonencode" {
value = local.a_jsonencode
}
# This results in:
a_jsonencode = "{\"hello\":\"world\"}"
jsonencode()
Encodes a value to a string using json.
TERMINAL
yamldecode()
locals {
a_yamldecode = yamldecode("hello: world")
}
output "a_yamldecode" {
value = local.a_yamldecode
}
# This returns:
a_yamldecode = {
"hello" = "world"
}
"B"
CODE EDITOR
locals {
a_yamlencode = yamlencode({ "a" : "b", "c" : "d" })
}
output "a_yamlencode" {
value = local.a_yamlencode
}
# This will return:
a_yamlencode = <<EOT
"a": "b"
"c": "d"
EOT
yamlencode()
Encodes a given value to a string using YAML.
TERMINAL
File system functions.
Functions to perform essential file operations.
Available functions: abspath, dirname, pathexpand, basename, file, fileexists, fileset, filebase64, templatefile.
file()
Reads the content of a file as a string and can be used in conjunction with other functions like jsondecode / yamldecode.
locals {
a_file = file("./a_file.txt")
}
output "a_file" {
value = local.a_file
}
# The output would be the content of the file called a_file as a string.
TERMINAL
#script.tftpl
#!/bin/sh
sudo mkdir ${name}
sudo touch ${name}.txt
# Inside Terraform configure file:
resource "aws_instance" "demo_vm" {
ami = var.ami
instance_type = var.type
user_data = templatefile("script.tftpl", {name = "John" })
key_name = "tftemplate"
}
templatefile()
Reads the file, changes the variables specified in the file between the interpolation syntax ${ … } with the ones from the vars map.
TERMINAL
IP network functions.
Functions to work with CIDR ranges.
Availability functions: cidrhost, cidrnetmask, cidrsubnet, cidrsubnets
cidrsubnet()
Calculates a subnet address within given IP network address prefix. Syntax: cidrsubnet(prefix, newbits, netnum)
> cidrsubnet("10.12.0.0/16", 8, 0)
"10.12.0.0/24"
> cidrsubnet("10.12.0.0/16", 8, 1)
"10.12.1.0/24"
> cidrsubnet("10.12.0.0/16", 8, 2)
"10.12.2.0/24"
> cidrsubnet("10.12.0.0/16", 8, 3)
"10.12.3.0/24“
TERMINAL
Hash and crypto functions.
Functions that work with hashing and cryptographic mechanisms.
Available functions: base64sha256, base64sha512, bcrypt, filebase64sha256, filebase64sha512, filemd5, filesha1, filesha256, filesha512, md5, rsadecrypt, sha1, sha256, sha512, uuid, uuidv5.
Using Terraform functions to write better code.
03
Put static files in a separate directory
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontServicePrincipal",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::${bucket}/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::${account_id}:distribution/${distribution_id}"
}
}
}
]
}
TERMINAL
data "template_file" "s3_logs_policy" {
template = "${file("${path.module}/templates/s3policy.tftpl")}"
vars = {
bucket = "test-dev-template-12242"
account_id = "1234567890"
distribution_id = "E15UHPQTKROC8Z"
}
}
resource "aws_s3_bucket" "s3_logs" {
bucket = "bucket-awslogs12242"
force_destroy = "true"
tags = {
Name = "bucket-awslogs12242"
Environment = "dev"
System = "test"
}
}
resource "aws_s3_bucket_policy" "s3_logs_policy" {
bucket = aws_s3_bucket.s3_logs.id
policy = data.template_file.s3_logs_policy.rendered
}
TERMINAL
Use merge() to create useful tags.
locals {
default_tags = {
managed = "terraform"
environment = ""
application = ""
monitored = "true"
}
}
tags = {
environment = "prod"
application = "primates"
monitored = "false"
}
> merge(local.default_tags, var.tags)
{
managed = "terraform"
environment = "prod"
application = "primates"
monitored = "false"
}
CODE EDITOR
Using try() when don’t know the form of data
variable "example" {
type = any
}
locals {
example = try(
[tostring(var.example)],
tolist(var.example),
)
}
CODE EDITOR
Use coalesce() to handle if/else logic
locals{
dev = var.environment == "DEV" ? "uksouth" : ""
uit = var.environment == "UIT" ? "ukwest" : ""
prod = var.environment != "PROD" && var.environment != "UIT" ? "useast2" : ""
region = coalesce(local.dev, local.uit, local.prod)
}
CODE EDITOR
Find values that exist or not and apply certain parameters using lookup().
tags = {
managed = "terraform"
environment = "prod"
application = "primates"
monitored = "true"
}
> lookup(var.tags, "monitored", "false")
true
## Now for a key that doesn't exist.
> lookup(var.tags, "shut_down_at_night", "false")
false
CODE EDITOR
Use format() for string concatenation.
variable "first-name" {
default = "Rahul"
}
variable "last-name" {
default = "Wagh"
}
variable "age" {
default = 33
}
output "formatted_string" {
value = "${format("My first name is %s and my last name is %s and my age is %d", var.first-name, var.last-name, var.age)}"
}
CODE EDITOR
hugs@hashicorp.com | learn.hashicorp.com | discuss.hashicorp.com
Thank You
hugs@hashicorp.com | learn.hashicorp.com | discuss.hashicorp.com