§ Migrate · Annexes
Function map
Every OpenTofu built-in function, with the CUE expression that replaces it, or the statement that none does.
On this page
OpenTofu 1.13 has 134 built-in functions, and tofu metadata functions -json lists them. CUE has a standard library of packages and a few builtins, and a comprehension replaces what neither covers. This page maps each function to its replacement, or says that there is none. pravo eval -c evaluates every CUE expression in the tables at the end of the page. The result column is that output.
Use a function by importing its package. An import that nothing uses is an error. The names after the imports are the ones the tables use.
| Package | Import | Holds |
|---|---|---|
| strings | "strings" |
ToUpper, Join, Split, Replace, TrimSpace, SliceRunes |
| list | "list" |
Range, Slice, Concat, Contains, Sort, Max, Sum, MinItems, MaxItems |
| struct | "struct" |
MinFields, MaxFields |
| math | "math" |
Abs, Ceil, Floor, Log, Pow |
| strconv | "strconv" |
Atoi, ParseInt, ParseFloat, ParseBool |
| regexp | "regexp" |
Find, FindAll, ReplaceAll, Match |
| net | "net" |
AddIP, ParseCIDR, InCIDR, QueryEscape, QueryUnescape, IPCIDR |
| path, time, uuid | "path", "time", "uuid" |
Base, Dir, FormatString, SHA1 |
| encoding | "encoding/base64", "encoding/json", "encoding/yaml", "encoding/csv", "encoding/hex" |
Encode, Marshal, Unmarshal, Decode |
| crypto | "crypto/md5", "crypto/sha1", "crypto/sha256", "crypto/sha512" |
Sum, Sum256, Sum512 |
| text/template | "text/template" |
Execute |
The CUE builtins need no import: len, div, mod, quo, rem, and the operators. A row that says No equivalent gives a replacement in the note when there is one.
The functions
Numeric
| OpenTofu | CUE | Result | Note |
|---|---|---|---|
abs(-5) |
math.Abs(-5) |
5 |
|
ceil(1.2) |
math.Ceil(1.2) |
2 |
|
floor(1.8) |
math.Floor(1.8) |
1 |
|
log(8, 2) |
math.Log(8) / math.Log(2) |
3.0 |
math.Log is the natural logarithm; the result is a float. |
max(1, 3, 2) |
list.Max([1, 3, 2]) |
3 |
list.Max takes one list. |
min(1, 3, 2) |
list.Min([1, 3, 2]) |
1 |
list.Min takes one list. |
parseint("ff", 16) |
strconv.ParseInt("ff", 16, 64) |
255 |
|
pow(2, 3) |
math.Pow(2, 3) |
8 |
|
signum(-5) |
[if _n > 0 {1}, if _n < 0 {-1}, 0][0] |
-1 |
No builtin; the list picks the first branch that holds. Here _n: -5. |
String
| OpenTofu | CUE | Result | Note |
|---|---|---|---|
chomp("hello\n") |
strings.TrimRight("hello\n", "\r\n") |
"hello" |
|
endswith("hello", "llo") |
strings.HasSuffix("hello", "llo") |
true |
|
format("%05d", 42) |
template.Execute("{{printf \"%05d\" .n}}", {n: 42}) |
"00042" |
text/template uses Go verbs and Go template syntax. For %s and %d alone, use interpolation: "\(a)-\(b)". |
formatlist("web-%s", ["a", "b"]) |
[for x in ["a", "b"] {"web-\(x)"}] |
["web-a", "web-b"] |
|
indent(2, "a\nb") |
strings.Replace("a\nb", "\n", "\n ", -1) |
Neither indents the first line. | |
join(",", ["a", "b"]) |
strings.Join(["a", "b"], ",") |
"a,b" |
|
lower("AbC") |
strings.ToLower("AbC") |
"abc" |
|
regex("[a-z]+", "123abc456") |
regexp.Find("[a-z]+", "123abc456") |
"abc" |
regexp.FindSubmatch returns the groups. |
regexall("[0-9]+", "a1b22c333") |
regexp.FindAll("[0-9]+", "a1b22c333", -1) |
["1", "22", "333"] |
|
replace("a-b-c", "-", "_") |
strings.Replace("a-b-c", "-", "_", -1) |
"a_b_c" |
-1 replaces every match. |
replace("a1b22", "/[0-9]+/", "#") |
regexp.ReplaceAll("[0-9]+", "a1b22", "#") |
"a#b#" |
The HCL form is replace with a pattern between slashes. |
split(",", "a,b,c") |
strings.Split("a,b,c", ",") |
["a", "b", "c"] |
The string comes first in CUE. |
startswith("hello", "he") |
strings.HasPrefix("hello", "he") |
true |
|
strcontains("hello", "ell") |
strings.Contains("hello", "ell") |
true |
|
strrev("abc") |
strings.Join(list.Reverse(strings.Split("abc", "")), "") |
"cba" |
No builtin; the string is split into characters. |
substr("hello", 1, 2) |
strings.SliceRunes("hello", 1, 3) |
"el" |
The end of SliceRunes is exclusive. |
templatestring("Hello, $${name}!", {name = "pravo"}) |
template.Execute("Hello, {{.name}}!", {name: "pravo"}) |
"Hello, pravo!" |
templatestring renders a template that a value holds. The placeholders of text/template are {{.name}}. |
title("hello world") |
strings.ToTitle("hello world") |
"Hello World" |
|
trim("xxhixx", "x") |
strings.Trim("xxhixx", "x") |
"hi" |
|
trimprefix("hello", "he") |
strings.TrimPrefix("hello", "he") |
"llo" |
|
trimsuffix("hello", "lo") |
strings.TrimSuffix("hello", "lo") |
"hel" |
|
trimspace(" hi ") |
strings.TrimSpace(" hi ") |
"hi" |
|
upper("abc") |
strings.ToUpper("abc") |
"ABC" |
Collection
| OpenTofu | CUE | Result | Note |
|---|---|---|---|
alltrue([true, false]) |
!list.Contains([true, false], false) |
false |
|
anytrue([false, true]) |
list.Contains([false, true], true) |
true |
|
chunklist([1, 2, 3, 4, 5], 2) |
[for i in list.Range(0, 5, 2) {list.Slice([1, 2, 3, 4, 5], i, [if i+2 < 5 {i+2}, 5][0])}] |
[[1, 2], [3, 4], [5]] |
No builtin. |
coalesce(null, "", "b", "c") |
[for x in [null, "", "b", "c"] if x != null && x != "" {x}][0] |
"b" |
|
coalescelist([], ["x"], ["y"]) |
[for l in [[], ["x"], ["y"]] if len(l) > 0 {l}][0] |
["x"] |
|
compact(["a", "", "b"]) |
[for x in ["a", "", "b"] if x != "" {x}] |
["a", "b"] |
|
concat([1, 2], [3]) |
list.Concat([[1, 2], [3]]) |
[1, 2, 3] |
|
contains(["a", "b"], "b") |
list.Contains(["a", "b"], "b") |
true |
|
distinct([1, 2, 1]) |
[for i, x in [1, 2, 1] if !list.Contains(list.Slice([1, 2, 1], 0, i), x) {x}] |
[1, 2] |
No builtin. list.UniqueItems checks that a list has no duplicates and does not remove them. |
element(["a", "b", "c"], 4) |
_l[mod(4, len(_l))] |
"b" |
Both wrap around. Here _l: ["a", "b", "c"]. |
flatten([[1], [2, [3]]]) |
list.FlattenN([[1], [2, [3]]], -1) |
[1, 2, 3] |
|
index(["a", "b", "c"], "b") |
[for i, x in _l if x == "b" {i}][0] |
1 |
Fails when the value is not in the list. |
keys({a = 1, b = 2}) |
[for k, _ in _m {k}] |
["a", "b"] |
Keys come in the order written. OpenTofu sorts them. Wrap with list.SortStrings to sort. Here _m: {a: 1, b: 2}. |
length(["a", "b", "c"]) |
len(_l) |
3 |
For a string len counts bytes. Count characters with len(strings.Split(s, "")). |
lookup({a = 1}, "z", 0) |
*_m["z"] | 0 |
0 |
The default marker goes on the lookup. With _m: {a: 1} the result is 0, and for a present key the value. |
matchkeys([1, 2, 3], ["a", "b", "c"], ["a", "c"]) |
[for i, k in ["a", "b", "c"] if list.Contains(["a", "c"], k) {[1, 2, 3][i]}] |
[1, 3] |
|
merge({a = 1, b = 2}, {c = 3}) |
_m & {c: 3} |
Unification, not an override: two values for one key are a conflict. See the note below. | |
one([5]) |
[if len([5]) == 1 {[5][0]}, null][0] |
5 |
|
range(0, 5, 2) |
list.Range(0, 5, 2) |
[0, 2, 4] |
range(3) is list.Range(0, 3, 1). |
reverse(["a", "b", "c"]) |
list.Reverse(_l) |
["c", "b", "a"] |
|
setintersection([1, 2, 3], [2, 3, 4]) |
[for x in [1, 2, 3] if list.Contains([2, 3, 4], x) {x}] |
[2, 3] |
|
setproduct([1, 2], ["x", "y"]) |
[for a in [1, 2] for b in ["x", "y"] {[a, b]}] |
[[1, "x"], [1, "y"], [2, "x"], [2, "y"]] |
|
setsubtract([1, 2, 3], [2]) |
[for x in [1, 2, 3] if !list.Contains([2], x) {x}] |
[1, 3] |
|
setunion([1, 2], [2, 3]) |
[for i, x in list.Concat([[1, 2], [2, 3]]) if !list.Contains(list.Slice(list.Concat([[1, 2], [2, 3]]), 0, i), x) {x}] |
[1, 2, 3] |
A concatenation without duplicates. |
slice(["a", "b", "c"], 1, 3) |
list.Slice(_l, 1, 3) |
["b", "c"] |
|
sort(["b", "a", "c"]) |
list.SortStrings(["b", "a", "c"]) |
["a", "b", "c"] |
list.Sort(l, list.Ascending) sorts numbers. |
sum([1, 2, 3]) |
list.Sum([1, 2, 3]) |
6 |
|
transpose({a = ["1", "2"], b = ["2"]}) |
{for k, v in {a: ["1", "2"], b: ["2"]} for x in v {"\(x)": [for k2, v2 in {a: ["1", "2"], b: ["2"]} if list.Contains(v2, x) {k2}]}} |
The result is a map from each value to the keys that hold it. | |
values({a = 1, b = 2}) |
[for _, v in _m {v}] |
[1, 2] |
In the order written. |
zipmap(["a", "b"], [10, 20]) |
{for i, k in ["a", "b"] {"\(k)": [10, 20][i]}} |
Encoding
| OpenTofu | CUE | Result | Note |
|---|---|---|---|
base64encode("hello") |
base64.Encode(null, "hello") |
"aGVsbG8=" |
|
base64decode("aGVsbG8=") |
"\(base64.Decode(null, "aGVsbG8="))" |
"hello" |
Decode returns bytes; interpolation makes a string. |
base64gunzip(x) |
No equivalent | Decode the value outside and pass it as a tag. | |
base64gzip("hello") |
No equivalent | Compute the value outside and pass it as a tag. | |
csvdecode("a,b\n1,2\n") |
csv.Decode("a,b\n1,2\n") |
[["a", "b"], ["1", "2"]] |
The result is a list of rows. Build maps from the header row with a comprehension. |
jsondecode("{\"a\": 1}") |
json.Unmarshal("{\"a\": 1}") |
a: 1 |
|
jsonencode({a = 1, b = [true]}) |
json.Marshal({a: 1, b: [true]}) |
"{\"a\":1,\"b\":[true]}" |
Keys come in the order written. OpenTofu sorts them. |
textdecodebase64("aGk=", "UTF-8") |
No equivalent | Only UTF-8 is read: use base64decode. |
|
textencodebase64("hi", "UTF-8") |
base64.Encode(null, "hi") |
"aGk=" |
Only UTF-8 is written. |
urldecode("a+b%26c") |
net.QueryUnescape("a+b%26c") |
"a b&c" |
|
urlencode("a b&c") |
net.QueryEscape("a b&c") |
"a+b%26c" |
|
yamldecode("a: 1\nb: [x]") |
yaml.Unmarshal("a: 1\nb: [x]") |
||
yamlencode({a = 1, b = ["x"]}) |
yaml.Marshal({a: 1, b: ["x"]}) |
The text can differ in layout from the OpenTofu output. |
Filesystem
| OpenTofu | CUE | Result | Note |
|---|---|---|---|
abspath("x") |
No equivalent | A specification is pure. Pass the path as a tag, or use -T for cwd. |
|
basename("/a/b/c.txt") |
path.Base("/a/b/c.txt") |
"c.txt" |
|
dirname("/a/b/c.txt") |
path.Dir("/a/b/c.txt") |
"/a/b" |
|
pathexpand("~/x") |
No equivalent | Pass the expanded path as a tag. | |
file("hello.txt") |
An embedded file | @embed(file=hello.txt, type=text). See below. |
|
fileexists("hello.txt") |
No equivalent | A file that is not there leaves its embedded value incomplete, and pravo eval -c fails. |
|
fileset(".", "*.txt") |
An embedded file | @embed(glob=*.txt, type=text) gives a struct of file names to content. |
|
filebase64("hello.txt") |
An embedded file | base64.Encode(null, _file) of an embedded file. |
|
templatefile("x.tpl", {n = 1}) |
An embedded file | template.Execute(_tpl, {n: 1}) with Go template syntax. |
Date and time
| OpenTofu | CUE | Result | Note |
|---|---|---|---|
formatdate("YYYY-MM-DD", "2023-11-14T22:13:20Z") |
time.FormatString("2006-01-02", "2023-11-14T22:13:20Z") |
"2023-11-14" |
The layout is the Go reference time, not YYYY-MM-DD. |
plantimestamp() |
No equivalent | Pass the time as a tag: now: string @tag(now). |
|
timeadd("2023-11-14T22:13:20Z", "1h") |
No equivalent | Pass the result as a tag: until: string @tag(until). |
|
timecmp("2023-11-14T22:13:20Z", "2023-11-15T00:00:00Z") |
"2023-11-14T22:13:20Z" < "2023-11-15T00:00:00Z" |
true |
For two UTC timestamps in the same layout. The result is a boolean, not -1, 0 or 1. |
timestamp() |
No equivalent | Pass the time as a tag: now: string @tag(now). |
Hash and crypto
| OpenTofu | CUE | Result | Note |
|---|---|---|---|
base64sha256("hello") |
base64.Encode(null, sha256.Sum256("hello")) |
"LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=" |
|
base64sha512("hello") |
base64.Encode(null, sha512.Sum512("hello")) |
||
bcrypt("secret") |
No equivalent | The bcrypt_hash attribute of random_password holds a hash. |
|
filebase64sha256("hello.txt") |
An embedded file | base64.Encode(null, sha256.Sum256(_file)). |
|
filebase64sha512("hello.txt") |
An embedded file | base64.Encode(null, sha512.Sum512(_file)). |
|
filemd5("hello.txt") |
An embedded file | hex.Encode(md5.Sum(_file)). |
|
filesha1("hello.txt") |
An embedded file | hex.Encode(sha1.Sum(_file)). |
|
filesha256("hello.txt") |
An embedded file | hex.Encode(sha256.Sum256(_file)). |
|
filesha512("hello.txt") |
An embedded file | hex.Encode(sha512.Sum512(_file)). |
|
md5("hello") |
hex.Encode(md5.Sum("hello")) |
"5d41402abc4b2a76b9719d911017c592" |
|
rsadecrypt(x, key) |
No equivalent | ||
sha1("hello") |
hex.Encode(sha1.Sum("hello")) |
"aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d" |
|
sha256("hello") |
hex.Encode(sha256.Sum256("hello")) |
||
sha512("hello") |
hex.Encode(sha512.Sum512("hello")) |
||
uuid() |
No equivalent | Evaluation is pure. The random_uuid resource of hashicorp/random makes a UUID once and records it. |
|
uuidv5("dns", "example.com") |
uuid.SHA1("6ba7b810-9dad-11d1-80b4-00c04fd430c8", "example.com") |
"cfbff0d1-9375-5685-968c-48ce8b15ae17" |
The first argument is the namespace UUID. This one is the DNS namespace. |
IP network
| OpenTofu | CUE | Result | Note |
|---|---|---|---|
cidrcontains("10.0.0.0/16", "10.0.1.5") |
net.InCIDR("10.0.1.5", "10.0.0.0/16") |
true |
The arguments change places. InCIDR takes an address and not a prefix. |
cidrhost("10.0.0.0/24", 5) |
net.AddIP(strings.Split("10.0.0.0/24", "/")[0], 5) |
"10.0.0.5" |
Valid when the prefix is the network address. |
cidrnetmask("10.0.0.0/24") |
net.ParseCIDR("10.0.0.0/24").prefix_mask |
"255.255.255.0" |
ParseCIDR also gives prefix_len, prefix_addr and broadcast_addr. |
cidrsubnet("10.0.0.0/16", 8, 2) |
net.AddIP(strings.Split("10.0.0.0/16", "/")[0], 2 * (math.Pow(2, 32-(strconv.Atoi(strings.Split("10.0.0.0/16", "/")[1])+8)) & int)) + "/\(strconv.Atoi(strings.Split("10.0.0.0/16", "/")[1])+8)" |
"10.0.2.0/24" |
IPv4. Write it once as a definition when you use it often. |
cidrsubnets("10.0.0.0/16", 4, 4) |
No equivalent | Write a comprehension over the cidrsubnet expression. |
Assertions
An assume function returns its value when the value meets a condition and fails otherwise. A constraint joined to the value does the same.
| OpenTofu | CUE | Result | Note |
|---|---|---|---|
assumeequal(1, 1) |
1 & 1 |
1 |
Two values that differ are a conflict. |
assumenotnull("x") |
"x" & !=null |
"x" |
|
assumestringprefix("pre-x", "pre-") |
"pre-x" & =~"^pre-" |
"pre-x" |
|
assumelistlength([1, 2], 1, 3) |
[1, 2] & list.MinItems(1) & list.MaxItems(3) |
[1, 2] |
|
assumelistlengthmax([1, 2], 3) |
[1, 2] & list.MaxItems(3) |
[1, 2] |
|
assumelistlengthmin([1, 2], 1) |
[1, 2] & list.MinItems(1) |
[1, 2] |
|
assumemaplength({a = 1}, 1, 3) |
{a: 1} & struct.MinFields(1) & struct.MaxFields(3) |
a: 1 |
|
assumemaplengthmax({a = 1}, 3) |
{a: 1} & struct.MaxFields(3) |
a: 1 |
|
assumemaplengthmin({a = 1}, 1) |
{a: 1} & struct.MinFields(1) |
a: 1 |
|
assumesetlength(toset([1, 2]), 1, 3) |
[1, 2] & list.MinItems(1) & list.MaxItems(3) |
[1, 2] |
CUE has lists and no sets. |
assumesetlengthmax(toset([1, 2]), 3) |
[1, 2] & list.MaxItems(3) |
[1, 2] |
|
assumesetlengthmin(toset([1, 2]), 1) |
[1, 2] & list.MinItems(1) |
[1, 2] |
Type conversion
| OpenTofu | CUE | Result | Note |
|---|---|---|---|
can(local.x) |
(_m["z"] != _|_) |
false |
Tests whether a reference or an expression is defined. |
convert("5", number) |
strconv.Atoi("5") |
5 |
strconv.ParseFloat parses a float, strconv.ParseBool a boolean, and "\(x)" makes a string. |
ephemeralasnull(x) |
No equivalent | pravo never records an ephemeral value. See Secrets and ephemeral values. | |
issensitive(x) |
No equivalent | Sensitivity belongs to the provider schema and to sensitive: true on an output. |
|
nonsensitive(x) |
No equivalent | Sensitivity belongs to the provider schema and to sensitive: true on an output. |
|
sensitive(x) |
No equivalent | Mark an output sensitive: true. |
|
tobool("true") |
strconv.ParseBool("true") |
true |
|
tolist(x) |
No equivalent | Not needed. CUE has one list type. | |
tomap(x) |
No equivalent | Not needed. CUE has one struct type. | |
tonumber("3.5") |
strconv.ParseFloat("3.5", 64) |
3.5 |
strconv.Atoi parses an integer. |
toset([1, 2, 1]) |
[for i, x in [1, 2, 1] if !list.Contains(list.Slice([1, 2, 1], 0, i), x) {x}] |
[1, 2] |
Only needed to remove duplicates. A loop over a list needs no set. |
tostring(5) |
"\(5)" |
"5" |
|
try(local.m.z, "d") |
*_m["z"] | "d" |
"d" |
The default marker goes on the first expression. |
type(x) |
No equivalent | Test a value against a type: (x & string) != _|_. OpenTofu has type in tofu console only. |
Differences that change a result
Five differences matter when a result feeds a resource.
- Order of keys.
keys,valuesandjsonencodesort the keys in OpenTofu. A CUE struct keeps the order you wrote. Sort withlist.SortStringswhen the order must not depend on the source. - Conflicts.
mergelets the last map win.&unifies, and two different values for one key are an error. Mark the value that may be replaced with*:{encrypted: *true | bool}. Pitfalls shows the error and the fix. - Defaults.
lookup(m, k, d)andtry(a, b)are*m[k] | dand*a | b. The marker goes on the expression that may fail. The formm[k] | *dreturnsdeven when the key exists. - Length.
lencounts bytes.length("héllo")is 5 in OpenTofu.len("héllo")is 6. - No clock, no randomness. There is no
timestamp()and nouuid(). A tag carries a time. A resource ofhashicorp/randomcarries a random value, recorded once.
Provider-defined functions
provider::kubernetes::manifest_decode(yaml) is a call in OpenTofu. In pravo a call is a declaration, built through the provider like a data source. The arguments go in args, and the answer is result. The function is pure, its result is never recorded, and it needs no cluster.
locals {
manifest = provider::kubernetes::manifest_decode(file("cm.yaml"))
}
output "kind" {
value = local.manifest.kind
}
package calls
import (
"pravo.dev/pravo"
k8s "pravo.dev/providers/hashicorp/kubernetes"
)
$k8s: k8s.#Provider
manifest: $k8s.#Function.manifest_decode & {
args: ["apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: demo\n"]
}
kind: pravo.#Output & {value: manifest.result.kind}
Graph: planned 0 of 1, 1 functions called.
Outputs:
kind: "ConfigMap"
In a root that declares the provider, pravo schema kubernetes/manifest_decode prints the parameters and the documentation of the function.
Files
A function that reads a file becomes an embedded file. The directory must be a CUE module, because @embed reads files relative to the module. The first line of the file is @extern(embed). The file name stays a plain string: @embed(file=hello.txt, type=text), and @embed(glob=*.txt, type=text) embeds a set.
hello
Hello, {{.name}}! n={{.n}}
module: "example.com/files@v0"
language: version: "v0.14.0"
@extern(embed)
package files
import (
"encoding/base64"
"encoding/hex"
"crypto/md5"
"crypto/sha256"
"text/template"
)
_file: _ @embed(file=hello.txt, type=text)
_tpl: _ @embed(file=greeting.tpl, type=text)
_all: _ @embed(glob=*.txt, type=text)
file: _file
fileset: [for name, _ in _all {name}]
filebase64: base64.Encode(null, _file)
filemd5: hex.Encode(md5.Sum(_file))
filesha256: hex.Encode(sha256.Sum256(_file))
templatefile: template.Execute(_tpl, {name: "pravo", n: 3})
file: """
hello
"""
fileset: ["hello.txt"]
filebase64: "aGVsbG8K"
filemd5: "b1946ac92492d2347c6235b4d2611184"
filesha256: "5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03"
templatefile: """
Hello, pravo! n=3
"""
The digests equal the MD5 and SHA-256 digests of the file. The template uses Go template syntax. A template written for templatefile with ${name} needs {{.name}}, and a %{ for } directive needs {{range}}.
Evaluate every row
The file below holds each CUE expression of the tables as a field, with the helper values _n, _l and _m. pravo eval -c fails when a field is not concrete. A row that stops evaluating fails the page.
package fn
import (
"math"
"strings"
"strconv"
"list"
"struct"
"regexp"
"net"
"path"
"time"
"uuid"
"text/template"
"encoding/base64"
"encoding/csv"
"encoding/hex"
"encoding/json"
"encoding/yaml"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
)
_n: -5
_l: ["a", "b", "c"]
_m: {a: 1, b: 2}
fn_abs: math.Abs(-5)
fn_ceil: math.Ceil(1.2)
fn_floor: math.Floor(1.8)
fn_log: math.Log(8) / math.Log(2)
fn_max: list.Max([1, 3, 2])
fn_min: list.Min([1, 3, 2])
fn_parseint: strconv.ParseInt("ff", 16, 64)
fn_pow: math.Pow(2, 3)
fn_signum: [if _n > 0 {1}, if _n < 0 {-1}, 0][0]
fn_chomp: strings.TrimRight("hello\n", "\r\n")
fn_endswith: strings.HasSuffix("hello", "llo")
fn_format: template.Execute("{{printf \"%05d\" .n}}", {n: 42})
fn_formatlist: [for x in ["a", "b"] {"web-\(x)"}]
fn_indent: strings.Replace("a\nb", "\n", "\n ", -1)
fn_join: strings.Join(["a", "b"], ",")
fn_lower: strings.ToLower("AbC")
fn_regex: regexp.Find("[a-z]+", "123abc456")
fn_regexall: regexp.FindAll("[0-9]+", "a1b22c333", -1)
fn_replace: strings.Replace("a-b-c", "-", "_", -1)
fn_replace_regex: regexp.ReplaceAll("[0-9]+", "a1b22", "#")
fn_split: strings.Split("a,b,c", ",")
fn_startswith: strings.HasPrefix("hello", "he")
fn_strcontains: strings.Contains("hello", "ell")
fn_strrev: strings.Join(list.Reverse(strings.Split("abc", "")), "")
fn_substr: strings.SliceRunes("hello", 1, 3)
fn_templatestring: template.Execute("Hello, {{.name}}!", {name: "pravo"})
fn_title: strings.ToTitle("hello world")
fn_trim: strings.Trim("xxhixx", "x")
fn_trimprefix: strings.TrimPrefix("hello", "he")
fn_trimsuffix: strings.TrimSuffix("hello", "lo")
fn_trimspace: strings.TrimSpace(" hi ")
fn_upper: strings.ToUpper("abc")
fn_alltrue: !list.Contains([true, false], false)
fn_anytrue: list.Contains([false, true], true)
fn_chunklist: [for i in list.Range(0, 5, 2) {list.Slice([1, 2, 3, 4, 5], i, [if i+2 < 5 {i+2}, 5][0])}]
fn_coalesce: [for x in [null, "", "b", "c"] if x != null && x != "" {x}][0]
fn_coalescelist: [for l in [[], ["x"], ["y"]] if len(l) > 0 {l}][0]
fn_compact: [for x in ["a", "", "b"] if x != "" {x}]
fn_concat: list.Concat([[1, 2], [3]])
fn_contains: list.Contains(["a", "b"], "b")
fn_distinct: [for i, x in [1, 2, 1] if !list.Contains(list.Slice([1, 2, 1], 0, i), x) {x}]
fn_element: _l[mod(4, len(_l))]
fn_flatten: list.FlattenN([[1], [2, [3]]], -1)
fn_index: [for i, x in _l if x == "b" {i}][0]
fn_keys: [for k, _ in _m {k}]
fn_length: len(_l)
fn_lookup: *_m["z"] | 0
fn_matchkeys: [for i, k in ["a", "b", "c"] if list.Contains(["a", "c"], k) {[1, 2, 3][i]}]
fn_merge: _m & {c: 3}
fn_one: [if len([5]) == 1 {[5][0]}, null][0]
fn_range: list.Range(0, 5, 2)
fn_reverse: list.Reverse(_l)
fn_setintersection: [for x in [1, 2, 3] if list.Contains([2, 3, 4], x) {x}]
fn_setproduct: [for a in [1, 2] for b in ["x", "y"] {[a, b]}]
fn_setsubtract: [for x in [1, 2, 3] if !list.Contains([2], x) {x}]
fn_setunion: [for i, x in list.Concat([[1, 2], [2, 3]]) if !list.Contains(list.Slice(list.Concat([[1, 2], [2, 3]]), 0, i), x) {x}]
fn_slice: list.Slice(_l, 1, 3)
fn_sort: list.SortStrings(["b", "a", "c"])
fn_sum: list.Sum([1, 2, 3])
fn_transpose: {for k, v in {a: ["1", "2"], b: ["2"]} for x in v {"\(x)": [for k2, v2 in {a: ["1", "2"], b: ["2"]} if list.Contains(v2, x) {k2}]}}
fn_values: [for _, v in _m {v}]
fn_zipmap: {for i, k in ["a", "b"] {"\(k)": [10, 20][i]}}
fn_base64encode: base64.Encode(null, "hello")
fn_base64decode: "\(base64.Decode(null, "aGVsbG8="))"
fn_csvdecode: csv.Decode("a,b\n1,2\n")
fn_jsondecode: json.Unmarshal("{\"a\": 1}")
fn_jsonencode: json.Marshal({a: 1, b: [true]})
fn_textencodebase64: base64.Encode(null, "hi")
fn_urldecode: net.QueryUnescape("a+b%26c")
fn_urlencode: net.QueryEscape("a b&c")
fn_yamldecode: yaml.Unmarshal("a: 1\nb: [x]")
fn_yamlencode: yaml.Marshal({a: 1, b: ["x"]})
fn_basename: path.Base("/a/b/c.txt")
fn_dirname: path.Dir("/a/b/c.txt")
fn_formatdate: time.FormatString("2006-01-02", "2023-11-14T22:13:20Z")
fn_timecmp: "2023-11-14T22:13:20Z" < "2023-11-15T00:00:00Z"
fn_base64sha256: base64.Encode(null, sha256.Sum256("hello"))
fn_base64sha512: base64.Encode(null, sha512.Sum512("hello"))
fn_md5: hex.Encode(md5.Sum("hello"))
fn_sha1: hex.Encode(sha1.Sum("hello"))
fn_sha256: hex.Encode(sha256.Sum256("hello"))
fn_sha512: hex.Encode(sha512.Sum512("hello"))
fn_uuidv5: uuid.SHA1("6ba7b810-9dad-11d1-80b4-00c04fd430c8", "example.com")
fn_cidrcontains: net.InCIDR("10.0.1.5", "10.0.0.0/16")
fn_cidrhost: net.AddIP(strings.Split("10.0.0.0/24", "/")[0], 5)
fn_cidrnetmask: net.ParseCIDR("10.0.0.0/24").prefix_mask
fn_cidrsubnet: net.AddIP(strings.Split("10.0.0.0/16", "/")[0], 2 * (math.Pow(2, 32-(strconv.Atoi(strings.Split("10.0.0.0/16", "/")[1])+8)) & int)) + "/\(strconv.Atoi(strings.Split("10.0.0.0/16", "/")[1])+8)"
fn_assumeequal: 1 & 1
fn_assumenotnull: "x" & !=null
fn_assumestringprefix: "pre-x" & =~"^pre-"
fn_assumelistlength: [1, 2] & list.MinItems(1) & list.MaxItems(3)
fn_assumelistlengthmax: [1, 2] & list.MaxItems(3)
fn_assumelistlengthmin: [1, 2] & list.MinItems(1)
fn_assumemaplength: {a: 1} & struct.MinFields(1) & struct.MaxFields(3)
fn_assumemaplengthmax: {a: 1} & struct.MaxFields(3)
fn_assumemaplengthmin: {a: 1} & struct.MinFields(1)
fn_assumesetlength: [1, 2] & list.MinItems(1) & list.MaxItems(3)
fn_assumesetlengthmax: [1, 2] & list.MaxItems(3)
fn_assumesetlengthmin: [1, 2] & list.MinItems(1)
fn_can: (_m["z"] != _|_)
fn_convert: strconv.Atoi("5")
fn_tobool: strconv.ParseBool("true")
fn_tonumber: strconv.ParseFloat("3.5", 64)
fn_toset: [for i, x in [1, 2, 1] if !list.Contains(list.Slice([1, 2, 1], 0, i), x) {x}]
fn_tostring: "\(5)"
fn_try: *_m["z"] | "d"
fn_abs: 5
fn_ceil: 2
fn_floor: 1
fn_log: 3.0
fn_max: 3
fn_min: 1
fn_parseint: 255
fn_pow: 8
fn_signum: -1
fn_chomp: "hello"
fn_endswith: true
fn_format: "00042"
fn_formatlist: ["web-a", "web-b"]
fn_indent: """
a
b
"""
fn_join: "a,b"
fn_lower: "abc"
fn_regex: "abc"
fn_regexall: ["1", "22", "333"]
fn_replace: "a_b_c"
fn_replace_regex: "a#b#"
fn_split: ["a", "b", "c"]
fn_startswith: true
fn_strcontains: true
fn_strrev: "cba"
fn_substr: "el"
fn_templatestring: "Hello, pravo!"
fn_title: "Hello World"
fn_trim: "hi"
fn_trimprefix: "llo"
fn_trimsuffix: "hel"
fn_trimspace: "hi"
fn_upper: "ABC"
fn_alltrue: false
fn_anytrue: true
fn_chunklist: [[1, 2], [3, 4], [5]]
fn_coalesce: "b"
fn_coalescelist: ["x"]
fn_compact: ["a", "b"]
fn_concat: [1, 2, 3]
fn_contains: true
fn_distinct: [1, 2]
fn_element: "b"
fn_flatten: [1, 2, 3]
fn_index: 1
fn_keys: ["a", "b"]
fn_length: 3
fn_lookup: 0
fn_matchkeys: [1, 3]
fn_merge: {
a: 1
b: 2
c: 3
}
fn_one: 5
fn_range: [0, 2, 4]
fn_reverse: ["c", "b", "a"]
fn_setintersection: [2, 3]
fn_setproduct: [[1, "x"], [1, "y"], [2, "x"], [2, "y"]]
fn_setsubtract: [1, 3]
fn_setunion: [1, 2, 3]
fn_slice: ["b", "c"]
fn_sort: ["a", "b", "c"]
fn_sum: 6
fn_transpose: {
"1": ["a"]
"2": ["a", "b"]
}
fn_values: [1, 2]
fn_zipmap: {
a: 10
b: 20
}
fn_base64encode: "aGVsbG8="
fn_base64decode: "hello"
fn_csvdecode: [["a", "b"], ["1", "2"]]
fn_jsondecode: {
a: 1
}
fn_jsonencode: "{\"a\":1,\"b\":[true]}"
fn_textencodebase64: "aGk="
fn_urldecode: "a b&c"
fn_urlencode: "a+b%26c"
fn_yamldecode: {
a: 1
b: ["x"]
}
fn_yamlencode: """
a: 1
b:
- x
"""
fn_basename: "c.txt"
fn_dirname: "/a/b"
fn_formatdate: "2023-11-14"
fn_timecmp: true
fn_base64sha256: "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="
fn_base64sha512: "m3HSJL1i83hdltRq0+o9czGb+8KJDKra4t/3JRlnPKcjI8PZm6XBHXx6zG4UuMXaDEZjR1wuXDre9G9zvN7AQw=="
fn_md5: "5d41402abc4b2a76b9719d911017c592"
fn_sha1: "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"
fn_sha256: "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
fn_sha512: "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043"
fn_uuidv5: "cfbff0d1-9375-5685-968c-48ce8b15ae17"
fn_cidrcontains: true
fn_cidrhost: "10.0.0.5"
fn_cidrnetmask: "255.255.255.0"
fn_cidrsubnet: "10.0.2.0/24"
fn_assumeequal: 1
fn_assumenotnull: "x"
fn_assumestringprefix: "pre-x"
fn_assumelistlength: [1, 2]
fn_assumelistlengthmax: [1, 2]
fn_assumelistlengthmin: [1, 2]
fn_assumemaplength: {
a: 1
}
fn_assumemaplengthmax: {
a: 1
}
fn_assumemaplengthmin: {
a: 1
}
fn_assumesetlength: [1, 2]
fn_assumesetlengthmax: [1, 2]
fn_assumesetlengthmin: [1, 2]
fn_can: false
fn_convert: 5
fn_tobool: true
fn_tonumber: 3.5
fn_toset: [1, 2]
fn_tostring: "5"
fn_try: "d"
Recap
- Import the package, call the function, and check the result with
pravo eval -e. - A function with no builtin is a comprehension, a resource of a provider, or a tag that carries the value.
- Order, conflicts and defaults differ from OpenTofu: read the five differences before you replace
merge,lookupandtry.
Check yourself
How do you write lookup(var.m, "k", "d")?
*m.k | "d", with the default marker on the lookup. The form m.k | *"d" returns the default even when k exists.
What replaces timestamp()?
A tag such as now: string @tag(now) carries a time that the caller supplies.
Next: Command map maps the OpenTofu commands.