Skip to main content
Version: main

CEL Libraries

kro includes a rich set of CEL function libraries from three sources: kro's own custom libraries, the cel-go standard extensions, and Kubernetes apiserver libraries.

Library Overview

LibraryProviderDocs
Hashkrokro, Go doc
JSONkrokro, Go doc
Randomkrokro, Go doc
Mapskrokro, Go doc
Index Mutationkrokro, Go doc
Omitkrokro, Go doc
Stringscel-gokro, Go doc
Lists (cel-go)cel-gokro, Go doc
Encoderscel-gokro, Go doc
Two-Variable Comprehensionscel-gokro, Go doc
URLskuberneteskro, Go doc, k8s.io
Regexkuberneteskro, Go doc, k8s.io
Quantitykuberneteskro, Go doc, k8s.io
Lists (k8s)kuberneteskro, Go doc, k8s.io
IPkuberneteskro, Go doc, k8s.io
CIDRkuberneteskro, Go doc, k8s.io
Semverkuberneteskro, Go doc, k8s.io

kro Libraries

Hash

Cryptographic and non-cryptographic hash functions. All return raw bytes; use "%x".format(...) for hex or base64.encode() for base64.

FunctionReturnsDescription
hash.fnv64a(string)bytesFNV-1a 64-bit hash. Fast, non-cryptographic. Recommended for most use cases.
hash.sha256(string)bytesSHA-256 cryptographic hash.
hash.md5(string)bytesMD5 hash.

Examples:

# SHA-256 as hex string (like sha256sum output)
checksum: ${"%x".format([hash.sha256(schema.spec.config)])}

# FNV-1a as hex (short, fast identifier)
id: ${"%x".format([hash.fnv64a(schema.spec.name + "-" + schema.spec.namespace)])}

# Base64-encoded hash (more compact)
encoded: ${base64.encode(hash.fnv64a(schema.metadata.uid))}

JSON

Parse and serialize JSON strings.

FunctionReturnsDescription
json.unmarshal(string)dynParse a JSON string into a CEL value (map, list, string, number, bool, or null).
json.marshal(dyn)stringSerialize a CEL value to a JSON string.

Examples:

# Parse a JSON string from a ConfigMap or user input
config: ${json.unmarshal(schema.spec.jsonConfig)}

# Access nested fields after parsing
dbHost: ${json.unmarshal(configmap.data.settings).database.host}

# Serialize structured data into a JSON string (e.g. for an annotation or env var)
configJson: ${json.marshal({"name": schema.spec.name, "replicas": schema.spec.replicas})}

Random

Deterministic random generation seeded by a stable value (e.g. resource UID), so results are reproducible across reconcile loops.

FunctionReturnsDescription
random.seededString(int, string)stringRandom alphanumeric string of given length, seeded by the second argument.
random.seededInt(int, int, string)intRandom integer in [min, max), seeded by the third argument.

Examples:

# Generate a stable random suffix
suffix: ${random.seededString(8, schema.metadata.uid)}

# Generate a stable random port
port: ${random.seededInt(30000, 32768, schema.metadata.uid)}

Maps

Map manipulation functions.

FunctionReturnsDescription
<map>.merge(map)mapMerge two maps. Keys from the second map overwrite keys in the first. Keys must be strings.

Examples:

# Merge user labels with default labels
labels: ${{"app": schema.spec.name, "managed-by": "kro"}.merge(schema.spec.extraLabels)}

# Layer overrides on top of defaults
config: ${{"timeout": "30s", "retries": "3"}.merge(schema.spec.overrides)}

Index Mutation

Pure list functions that return a new list without modifying the input.

FunctionSignatureDescription
lists.setAtIndexlist(T), int, T -> list(T)Replace the element at index. Index must be in [0, size(list)).
lists.insertAtIndexlist(T), int, T -> list(T)Insert value before index. Use index == size(list) to append. Index must be in [0, size(list)].
lists.removeAtIndexlist(T), int -> list(T)Remove the element at index. Index must be in [0, size(list)).

Examples:

# Replace the second tag
tags: ${lists.setAtIndex(schema.spec.tags, 1, "new-tag")}

# Prepend an environment variable
envVars: ${lists.insertAtIndex(schema.spec.envVars, 0, "DEBUG=true")}

# Remove the first port
ports: ${lists.removeAtIndex(schema.spec.ports, 0)}

# Chain operations: swap first two elements
swapped: ${lists.setAtIndex(lists.setAtIndex(schema.spec.items, 0, schema.spec.items[1]), 1, schema.spec.items[0])}

Omit

The omit() sentinel tells kro to remove a field from the rendered resource. This is useful for conditionally excluding fields.

FunctionReturnsDescription
omit()omitReturns a sentinel value that causes the field to be excluded from the output.

Examples:

# Conditionally include a field
nodeSelector: ${schema.spec.pinToNode ? {"kubernetes.io/hostname": schema.spec.nodeName} : omit()}
note

omit() is only allowed in resource template fields. It is rejected in includeWhen, readyWhen, and forEach expressions.

cel-go Libraries

Strings

Extended string manipulation functions from cel-go/ext.

FunctionReturnsDescription
<string>.charAt(int)stringCharacter at position.
<string>.indexOf(string)intIndex of first occurrence, or -1.
<string>.indexOf(string, int)intSame, starting search from offset.
<string>.lastIndexOf(string)intIndex of last occurrence, or -1.
<string>.lastIndexOf(string, int)intSame, with max search position.
<string>.lowerAscii()stringLowercase ASCII characters.
<string>.upperAscii()stringUppercase ASCII characters.
<string>.replace(string, string)stringReplace all occurrences.
<string>.replace(string, string, int)stringReplace up to N occurrences.
<string>.split(string)list(string)Split by separator.
<string>.split(string, int)list(string)Split with limit.
<string>.substring(int)stringSubstring from position to end.
<string>.substring(int, int)stringSubstring from start (inclusive) to end (exclusive).
<string>.trim()stringRemove leading and trailing whitespace.
<string>.reverse()stringReverse the string.
<list(string)>.join()stringConcatenate list elements.
<list(string)>.join(string)stringConcatenate with separator.
<string>.format(list)stringPrintf-style formatting (%s, %d, %f, %e, %b, %x, %X, %o).
strings.quote(string)stringEscape a string for safe printing.

Examples:

# Printf-style formatting
roleArn: ${"arn:aws:iam::%s:role/%s".format([schema.spec.accountId, schema.spec.roleName])}

# Join list elements with a separator
labelStr: ${schema.spec.tags.join(",")}

# Split a domain name
parts: ${schema.spec.fqdn.split(".")}

# Substring extraction
prefix: ${schema.spec.name.substring(0, 5)}

# Case conversion
lower: ${schema.spec.input.lowerAscii()}
upper: ${schema.spec.input.upperAscii()}

# Search within strings
hasPort: ${schema.spec.endpoint.indexOf(":") >= 0}

# Replace characters
sanitized: ${schema.spec.name.replace("_", "-")}

# Trim whitespace
cleaned: ${schema.spec.input.trim()}

# Hex encoding via %x (useful for hash output)
checksum: ${"%x".format([hash.sha256(schema.spec.data)])}

Lists (cel-go)

Extended list manipulation functions from cel-go/ext.

FunctionReturnsDescription
<list>.slice(int, int)listSub-list from start (inclusive) to end (exclusive).
<list>.flatten()listFlatten nested lists one level deep.
<list>.flatten(int)listFlatten up to N levels.
<list>.sort()listSort comparable elements.
<list>.sortBy(var, keyExpr)listSort by a key expression.
<list>.distinct()listRemove duplicate elements.
<list>.reverse()listReverse element order.
lists.range(int)list(int)Generate [0, 1, ..., n-1].

Examples:

# Slice a list
subset: ${schema.spec.items.slice(0, 3)}

# Sort and deduplicate
tags: ${schema.spec.tags.sort().distinct()}

# Sort by a field
ordered: ${items.sortBy(i, i.data.priority)}

# Generate index range
indices: ${lists.range(schema.spec.count)}

# Flatten nested lists
flat: ${schema.spec.nestedPorts.flatten()}

Encoders

Base64 encoding and decoding from cel-go/ext.

FunctionReturnsDescription
base64.encode(bytes)stringEncode bytes to base64 string.
base64.decode(string)bytesDecode base64 string to bytes.

Examples:

# Encode a hash to base64
encoded: ${base64.encode(hash.sha256(schema.spec.data))}

# Encode a string as base64 (e.g. for a Kubernetes Secret)
secret: ${base64.encode(bytes(schema.spec.password))}

# Decode a base64-encoded string
decoded: ${string(base64.decode(schema.spec.encodedValue))}

Two-Variable Comprehensions

Two-variable versions of list/map comprehension macros from cel-go/ext. These provide access to both the key/index and value in each iteration.

MacroDescription
<list|map>.all(k, v, predicate)True if all entries satisfy the predicate.
<list|map>.exists(k, v, predicate)True if any entry satisfies the predicate.
<list|map>.existsOne(k, v, predicate)True if exactly one entry satisfies the predicate.
<list|map>.transformList(k, v, transform)Convert map/list to a list by evaluating transform for each entry.
<list|map>.transformList(k, v, filter, transform)Same with a filter predicate.
<list|map>.transformMap(k, v, transform)Transform values while preserving keys.
<list|map>.transformMap(k, v, filter, transform)Same with a filter predicate.
<list|map>.transformMapEntry(k, v, transform)Build new key-value pairs (transform must return a single-entry map).
<list|map>.transformMapEntry(k, v, filter, transform)Same with a filter predicate.

Examples:

# Extract keys from a map
keys: ${{ "app": "nginx", "version": "1.19" }.transformList(k, v, k)}
# -> ["app", "version"]

# Transform map values
scaled: ${{ "cpu": 2, "memory": 4 }.transformMap(k, v, v * 2)}
# -> {"cpu": 4, "memory": 8}

# Filter and transform
filtered: ${{ "a": 1, "b": 5, "c": 3 }.transformMap(k, v, v > 1, v * 10)}
# -> {"b": 50, "c": 30}

# Swap keys and values
swapped: ${{ "us-east-1": "primary", "eu-west-1": "secondary" }.transformMapEntry(k, v, {v: k})}
# -> {"primary": "us-east-1", "secondary": "eu-west-1"}

# Build formatted strings from key-value pairs
envVars: ${{ "APP": "nginx", "PORT": "8080" }.transformList(k, v, k + "=" + v)}
# -> ["APP=nginx", "PORT=8080"]

# Two-variable all/exists on a map
allDifferent: ${{ "hello": "world", "taco": "bell" }.all(k, v, k != v)}

Kubernetes Libraries

URLs

Parse and inspect URLs. The URL must be an absolute URI or an absolute path.

FunctionReturnsDescription
url(string)URLParse a string into a URL. Errors if invalid.
isURL(string)boolReturns true if the string is a valid URL.
<URL>.getScheme()stringReturns the URL scheme (e.g. https). Empty string if absent.
<URL>.getHost()stringReturns the host including port (e.g. example.com:80). IPv6 addresses are bracketed.
<URL>.getHostname()stringReturns the hostname without port. IPv6 addresses returned without brackets.
<URL>.getPort()stringReturns the port, or empty string if not specified.
<URL>.getEscapedPath()stringReturns the URL-escaped path.
<URL>.getQuery()map(string, list(string))Returns query parameters as a map.

Examples:

# Extract the host from an endpoint URL
host: ${url(schema.spec.endpoint).getHostname()}

# Validate a URL
valid: ${isURL(schema.spec.callbackUrl)}

# Build a connection using URL parts
port: ${url(database.status.endpoint).getPort()}

Regex

Regular expression matching and extraction via k8s.io/apiserver/pkg/cel/library.

FunctionReturnsDescription
<string>.find(string)stringReturns the first regex match, or empty string.
<string>.findAll(string)list(string)Returns all non-overlapping matches.
<string>.findAll(string, int)list(string)Returns up to N matches.
note

The built-in CEL <string>.matches(string) function is always available (it's part of the CEL standard library, not this extension). It returns true if the entire string matches the regex.

Examples:

# Validate a resource name pattern
valid: ${schema.spec.name.matches('^[a-z][a-z0-9-]*$')}

# Extract version numbers from a string
versions: ${"v1.2.3 and v4.5.6".findAll('[0-9]+\\.[0-9]+\\.[0-9]+')}

# Find first match
version: ${schema.spec.image.find('[0-9]+\\.[0-9]+\\.[0-9]+')}

Quantity

Work with Kubernetes resource quantities (e.g. 100m, 1Gi, 500Mi).

FunctionReturnsDescription
quantity(string)QuantityParse a Kubernetes quantity string.
isQuantity(string)boolTrue if the string is a valid quantity.
<Quantity>.add(Quantity|int)QuantitySum of two quantities.
<Quantity>.sub(Quantity|int)QuantityDifference of two quantities.
<Quantity>.isLessThan(Quantity)boolComparison.
<Quantity>.isGreaterThan(Quantity)boolComparison.
<Quantity>.compareTo(Quantity)intReturns -1, 0, or 1.
<Quantity>.asInteger()intScaled value as integer. Errors on overflow or precision loss.
<Quantity>.asApproximateFloat()floatApproximate float value.
<Quantity>.isInteger()boolTrue if the quantity can be represented as an integer without loss.
<Quantity>.sign()intReturns 1, -1, or 0.

Examples:

# Compare memory requests
needsMore: ${quantity(schema.spec.memoryRequest).isLessThan(quantity('1Gi'))}

# Arithmetic on quantities
total: ${quantity(schema.spec.cpuRequest).add(quantity('100m'))}
remaining: ${quantity('2Gi').sub(quantity(schema.spec.memoryRequest))}

# Validate a resource quantity
valid: ${isQuantity(schema.spec.cpuLimit)}

# Convert whole-number quantities to integer
megabytes: ${quantity(schema.spec.storageSize).asInteger()}

Lists (k8s)

Kubernetes-specific list utility functions from k8s.io/apiserver/pkg/cel/library.

FunctionReturnsDescription
<list>.isSorted()boolTrue if elements are in sorted order.
<list>.sum()TSum of numeric or duration elements. Returns 0 for empty lists.
<list>.min()TMinimum element. Errors on empty list.
<list>.max()TMaximum element. Errors on empty list.
<list>.indexOf(T)intIndex of first occurrence, or -1.
<list>.lastIndexOf(T)intIndex of last occurrence, or -1.

Examples:

# Check if ports are sorted
sorted: ${schema.spec.ports.isSorted()}

# Sum all replica counts
totalReplicas: ${schema.spec.replicaCounts.sum()}

# Find min/max
minPort: ${schema.spec.ports.min()}
maxPort: ${schema.spec.ports.max()}

# Find element index
idx: ${schema.spec.tags.indexOf("production")}

IP

Parse and inspect IPv4 and IPv6 addresses. IPv4-mapped IPv6 addresses and addresses with zones are not allowed.

FunctionReturnsDescription
ip(string)IPParse a string into an IP address. Errors if invalid.
isIP(string)boolReturns true if the string is a valid IPv4 or IPv6 address.
ip.isCanonical(string)boolReturns true if the string is in canonical IP form.
<IP>.family()intReturns 4 or 6.
<IP>.isLoopback()boolTrue for 127.x.x.x (v4) or ::1 (v6).
<IP>.isGlobalUnicast()boolTrue for globally routable addresses.
<IP>.isLinkLocalUnicast()boolTrue for link-local unicast (169.254.x.x / fe80::/10).
<IP>.isLinkLocalMulticast()boolTrue for link-local multicast (224.0.0.x / ff00::/8).
<IP>.isUnspecified()boolTrue for 0.0.0.0 or ::.
string(<IP>)stringConvert back to string.

Examples:

# Validate an IP address from user input
ready: ${isIP(schema.spec.clusterIP)}

# Check IP family for dual-stack logic
family: ${string(ip(schema.spec.podIP).family())}

# Gate on global unicast
isRoutable: ${ip(schema.spec.address).isGlobalUnicast()}

# Canonical form check
canonical: ${ip.isCanonical(schema.spec.ipv6Addr)}

CIDR

Parse and inspect CIDR subnet notation. Works with both IPv4 and IPv6.

FunctionReturnsDescription
cidr(string)CIDRParse a CIDR string (e.g. 192.168.0.0/24). Errors if invalid.
isCIDR(string)boolReturns true if the string is valid CIDR notation.
<CIDR>.containsIP(string|IP)boolTrue if the CIDR contains the given IP.
<CIDR>.containsCIDR(string|CIDR)boolTrue if the CIDR fully contains the given subnet.
<CIDR>.ip()IPReturns the address part of the CIDR.
<CIDR>.prefixLength()intReturns the prefix length in bits.
<CIDR>.masked()CIDRReturns the CIDR in canonical masked form.
string(<CIDR>)stringConvert back to string.

Examples:

# Check if a pod IP falls within the service CIDR
inRange: ${cidr(schema.spec.serviceCIDR).containsIP(pod.status.podIP)}

# Validate that a user-provided CIDR is valid
valid: ${isCIDR(schema.spec.subnetCIDR)}

# Extract prefix length
prefixLen: ${string(cidr(schema.spec.subnetCIDR).prefixLength())}

# Check subnet containment
isSubnet: ${cidr('10.0.0.0/8').containsCIDR(schema.spec.vpcCIDR)}

Semver

Parse and compare semantic versions. Supports optional normalization to handle common non-strict formats like v1.0 or 01.02.03.

FunctionReturnsDescription
semver(string)SemverParse a strict semver string (e.g. 1.2.3).
semver(string, bool)SemverParse with normalization (strips v prefix, fills missing parts, removes leading zeros).
isSemver(string)boolReturns true if the string is a valid semver.
isSemver(string, bool)boolSame with optional normalization.
<Semver>.major()intMajor version number.
<Semver>.minor()intMinor version number.
<Semver>.patch()intPatch version number.
<Semver>.isGreaterThan(Semver)boolTrue if receiver > operand.
<Semver>.isLessThan(Semver)boolTrue if receiver < operand.
<Semver>.compareTo(Semver)intReturns -1, 0, or 1.

Examples:

# Validate a version string
validVersion: ${isSemver(schema.spec.version)}

# Compare versions
ready: ${semver(schema.spec.version).compareTo(semver('2.0.0')) >= 0}

# Extract major version for image tag
majorTag: ${string(semver(schema.spec.appVersion).major())}

# Tolerant parsing with normalization (accepts "v1.0", "01.02.03", etc.)
valid: ${isSemver(schema.spec.version, true)}
parsed: ${semver(schema.spec.version, true).minor()}

For the complete CEL language reference, see the CEL language definitions.

Brought to you with ♥ by SIG Cloud Provider