Where you see these functions
Any HCL expression can call a function: inside resource, data, variable.default, locals, output. The syntax is ordinary: func(arg1, arg2).
All string functions are pure. They make no network requests, write nothing to disk, and run at plan time. That means you can drop them into any expression without side effects.
format, like printf
A template plus arguments. It returns a formatted string.
format("user-%s-%03d", "alice", 7) # "user-alice-007"format("%.2f USD", 19.999) # "19.99 USD"format("https://%s:%d", "api.example", 8080) # "https://api.example:8080"Useful verbs:
%s, a string%d, an integer%f,%.2f, a float with two digits after the point%t, a bool%v, anything (as is)
There is also formatlist, which applies format to a list:
formatlist("https://%s.example.com", ["api", "www", "admin"])# ["https://api.example.com", "https://www.example.com", "https://admin.example.com"]
join and split
Turn a list into a string and back.
join(", ", ["a", "b", "c"]) # "a, b, c"join("/", ["my", "bucket", "path"]) # "my/bucket/path"split(",", "us-east-1,us-east-2,eu-central-1")# ["us-east-1", "us-east-2", "eu-central-1"]
A common case is collecting a list of CIDRs into one string for a description:
description = "Allowed: ${join(", ", var.cidrs)}"replace, substring or regex
It has two forms. By default it matches a substring:
replace("hello-world", "-", "_") # "hello_world"With a regex it searches for and replaces a pattern. The marker for regex is a / at the start and end:
replace("v1.2.3-rc.4", "/-rc\\.\\d+$/", "") # "v1.2.3"replace("ARN-12345-XXX", "/[0-9]+/", "###") # "ARN-###-XXX"Capture groups with $N:
replace("user@example.com", "/^(.+)@(.+)$/", "$1 at $2") # "user at example.com"If the regex is invalid, terraform validate fails with a message. Debug it first in terraform console.
regex and regexall
Pull out a match instead of replacing:
regex("[0-9]+", "version-42-build-7") # "42", the first matchregexall("[0-9]+", "version-42-build-7") # ["42", "7"], all of themHandy when you need to extract a number from a resource name or an AMI.
lower, upper, title
lower("AWS") # "aws"upper("us-east-1") # "US-EAST-1"title("hello world") # "Hello World"A frequent case is normalizing resource names:
bucket = lower("${var.project}-${var.env}-${random_id.suffix.hex}")# AWS does not allow uppercase in bucket names
trim and its variants
Strip characters from the start and end:
trimspace(" hello ") # "hello" , spaces and newlinestrim("xxxhelloxxx", "x") # "hello" , the character x on both sidestrimprefix("https://api.example", "https://") # "api.example"trimsuffix("logs.json", ".json") # "logs"substr, substring
substr("hello world", 0, 5) # "hello"substr("abc-12345", 4, 5) # "12345"The third argument is a length, not an end index.
length for a string
Although length is usually about collections, for a string it counts characters (not bytes!):
length("hello") # 5length("résumé") # 6 (non-ASCII characters, counted as codepoints)This matters when you work against UTF-8 limits in AWS (a bucket name is 3 to 63 characters).
Concatenation: prefer interpolation over +
HCL has no + operator for strings. Use interpolation:
# This does not work:
# "hello" + "world"
# This does:
"${var.a}-${var.b}"If the concatenation is complex, move it into locals (see tf-locals).
Pitfalls
-
formatis strict about types.format("%d", "42")fails because"42"is a string. Useformat("%s", "42")orparseint("42", 10). -
A regex backslash is escaped twice. In
"/\\d+/", the pattern is\d+. HCL eats one\as a string escape character. -
replacewithout/.../is a substring.replace("a.b", ".", "/")replaces the literal dot, not "any character". For regex the slashes are required:replace("a.b", "/./", "/"). -
upperandlowerdo not work on lists.lower(["A", "B"])fails. Use[for s in arr : lower(s)](see tf-functions-collection). -
titleis naive.title("hello WORLD")returns"Hello World". Each word gets a capital, the rest goes lowercase. For proper names that may be wrong. -
A string's
lengthis in characters, not bytes. On multibyte strings it can surprise you. If you need bytes, convert throughbase64encode+length(a rough hack). -
format and other printf-like functions do not interpolate HCL expressions.
format("%s", var.x)is fine.format("Hello, ${var.name}")is redundant: the expression can simply be"Hello, ${var.name}".