1 of 60

2 of 60

Terraform functions deep dive

Uderstand how to use Terraform functions to write better code.

3 of 60

DevOps Engineer�she/her��@holmes_shin

Yen Trinh

4 of 60

Terraform functions overview.

01

5 of 60

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.

6 of 60

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)

7 of 60

What is Terraform functions?

terraform console

You can use “terraform console” to have fun with Terraform functions…

8 of 60

Why use Terraform functions?

  • Make your code more dynamic and ensure your configuration is DRY.

  • Allow you to perform various operations, such as converting expressions to different data types, calculating lengths, and building complex variables.

9 of 60

Deep dive in some core functions.

02

10 of 60

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

11 of 60

String functions.

Functions that we focus today.

  • format()
  • split()
  • join()
  • substr()
  • replace()
  • regex()

join

12 of 60

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

13 of 60

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

14 of 60

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

15 of 60

substr()

# Returns hello

locals {

message = "hello world"

substring = "${substr(local.message, 0, 5)}"

}

# Returns world

"${substr(local.message, -5, -1)}"

CODE EDITOR

16 of 60

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

17 of 60

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

18 of 60

Numeric functions.

Functions to perform mathematical operations on numeric values.

Available functions: abs, ceil, floor, log, max, min, parseint, pow, signum

19 of 60

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

20 of 60

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.

21 of 60

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

22 of 60

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

23 of 60

> 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

24 of 60

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

25 of 60

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

26 of 60

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

27 of 60

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

28 of 60

range()

Creates a range of numbers:

  • One argument(limit)
  • Two arguments(initial_value, limit)
  • Three arguments(initial_value, limit, step)

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

29 of 60

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

30 of 60

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

31 of 60

Type conversion functions.

Functions to convert data types.

Available functions: can, nonsensitive, sensitive, tobool, tolist, tomap, tonumber, toset, tostring, try, type

32 of 60

Type conversion functions.

ToType Functions.

  1. tonumber(argument) – change a string to a number.
  2. tostring(argument) – Changes a number/bool/string/null to a string.
  3. tobool(argument) – Changes a string (only “true” or “false”)/bool/null to a bool.
  4. tolist(argument) – Changes a set to a list.
  5. toset(argument) – Changes a list to a set.
  6. tomap(argument) – Converts its argument to a map.

33 of 60

# 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

34 of 60

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

35 of 60

Date and time functions.

Functions for date and time manipulation.

Available functions: formatdate, timeadd, timecmp, timestamp.

36 of 60

timestamp()

locals {

current_time = timestamp()

}

output "current_time" {

value = local.current_time

}

# Apply result:

current_time = "2023-04-18T13:35:50Z"

"B"

CODE EDITOR

37 of 60

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

38 of 60

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

39 of 60

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.

40 of 60

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

41 of 60

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

42 of 60

yamldecode()

locals {

a_yamldecode = yamldecode("hello: world")

}

output "a_yamldecode" {

value = local.a_yamldecode

}

# This returns:

a_yamldecode = {

"hello" = "world"

}

"B"

CODE EDITOR

43 of 60

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

44 of 60

File system functions.

Functions to perform essential file operations.

Available functions: abspath, dirname, pathexpand, basename, file, fileexists, fileset, filebase64, templatefile.

45 of 60

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

46 of 60

#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

47 of 60

IP network functions.

Functions to work with CIDR ranges.

Availability functions: cidrhost, cidrnetmask, cidrsubnet, cidrsubnets

48 of 60

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

49 of 60

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.

50 of 60

Using Terraform functions to write better code.

03

51 of 60

Put static files in a separate directory

  • Place lengthy HereDocs in external files, separate from their HCL. Reference them with the file() function.
  • For files that are read in by using the Terraform templatefile() function, use the file extension .tftpl. Templates must be placed in a templates/ directory.

52 of 60

{

"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

53 of 60

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

54 of 60

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

55 of 60

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

56 of 60

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

57 of 60

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

58 of 60

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

59 of 60

hugs@hashicorp.com | learn.hashicorp.com | discuss.hashicorp.com

60 of 60

Thank You

hugs@hashicorp.com | learn.hashicorp.com | discuss.hashicorp.com