Skip to navigation

Values and expressions

Rivet evaluates expressions while compiling. The host only receives the results.

ValueExampleNotes
String"hello"No implicit boolean or number-to-string conversion for resource fields.
BooleantrueUsed for actual boolean attributes.
Number1001, 1.5Integers use signed 64-bit storage; fractional expressions use finite double precision. Resource integer fields and external number inputs reject fractions.
NullnullAccepted only where a field’s null policy permits it.
List["app", "ops"]Element types are checked by the consuming resource.
Object{ APP_ENV = "production" }Used for maps such as command environments.
Handlefile.configRefers to a declaration and carries dependency provenance.
Factfacts.processors.countSnapshot values retain their JSON types.
Trusted claimtrusted.nodeCredential-derived values from authenticated service compilation.

Parentheses group expressions. Declared types include scalars, resource handles, and nested list/set/map/object/tuple types. Collections can be inputs, locals, module arguments, or outputs. See types.

Interpolation

locals {
directory = "/var/lib/example"
filename = "message.txt"
}
file "message" {
path = "${local.directory}/${local.filename}"
content = "Target: ${facts.distribution} ${facts.version}\n"
}

Strings, finite numbers, and booleans can be interpolated. Null, collections, and handles require explicit conversion or encoding. Numeric facts support interpolation and numeric outputs; ordinary resource parameters retain strict types. Templates support heredocs, directives, and whitespace stripping. Expression nesting is limited to 128.

Operators

variable "environment" {
type = string
default = "production"
}
locals {
production = var.environment == "production"
workers = facts.processors.count * 2
}
output "replicas" {
type = number
value = local.production && local.workers >= 4 ? local.workers : 1
}
OperatorsOperandsResult
-x, !xnumber, boolNegation and boolean not.
*, /, %, +, -numbersInteger results stay exact and fail on overflow; inexact / yields a fraction. Division or remainder by zero fails.
<, <=, >, >=numbersStrings and other values are errors; nothing is converted.
==, !=any valuesStructural equality without conversion: 1 == 1.0 but "1" != 1.
&&, ||boolsShort-circuit: the right side is not evaluated when the left side decides the result.

Precedence follows HCL: unary, multiplicative, additive, comparison, equality, &&, ||, then the conditional. A result carries the dependencies and sensitivity of every operand that was evaluated.

For expressions

variable "users" {
type = map(object({ admin = bool, shell = string }))
default = {
alice = { admin = true, shell = "/bin/bash" }
bob = { admin = false, shell = "/bin/sh" }
}
}
locals {
admins = [for name, user in var.users : name if user.admin]
shells = { for name, user in var.users : user.shell => name... }
}
file "admins" {
path = "/etc/rivet-admins"
content = "${join("\n", local.admins)}\n"
}

[for v in collection : value] builds a list; {for k, v in collection : key => value} builds a map. Lists bind index and element, maps bind key (in sorted order) and value, and sets bind each element as both. An optional if filter drops elements. Duplicate map keys fail unless the value ends in ..., which groups values into lists. Keys convert strings, numbers, and bools to strings.

Loop variables are visible only within the expression and may shadow other names. Each element keeps the annotations it actually read. A sensitive filter or collection shape makes the result sensitive, and a sensitive key makes the map’s keys sensitive. Template loops and for expressions share a compilation-wide 100,000-iteration budget.

Splats

file "copies" {
count = 2
path = "/srv/copy-${count.index}"
}
output "copy_paths" {
type = list(string)
value = file.copies[*].path
}

list[*].attr applies every following traversal step to each element and returns a list. list.*.attr applies only the attribute lookups immediately after it, so list.*.attr[0] indexes the resulting list. Splatting null yields [], and any other non-list value becomes a one-element list.

References

Use var.name, local.name, file.label, or an exported static property such as service.app.name. Local values can refer forward to other locals or declarations. Cycles are errors.

A handle is declared work, not an apply result. You can read only supported static attributes; omitted optional attributes cannot be discovered from the host. See resource conventions for the export list.

Resource provenance survives locals and interpolation. Referring to file.config.path in a consuming resource makes the config a prerequisite. An independently written equal path string does not create that relationship.

Fact paths

Use dot traversal, map keys, array indices, splats, and for expressions:

output "root_filesystem" {
type = string
value = facts.mounts["/"].filesystem
}
output "cpu_count" {
type = number
value = facts.processors.count
}
output "ext4_mounts" {
type = list(string)
value = [for path, mount in facts.mounts : path if mount.filesystem == "ext4"]
}

These examples need a collected snapshot with the relevant fields. Baseline-only assumptions contain just the four legacy fact aliases. Indices may be evaluated expressions. Missing keys and invalid indices fail; integer facts can supply numeric indices/count. Optional inventory fields may be absent; use try or can for guarded access. Decoded collections such as jsondecode(file("assets/config.json")) support computed traversal too.

Conditionals and collection functions

variable "enabled" {
type = bool
default = true
}
locals {
names = toset(["app", "ops", "app"])
message = var.enabled ? "enabled" : "disabled"
}
output "name_count" {
type = number
value = length(local.names)
}
file "summary" {
path = "/etc/rivet-summary"
content = "${local.message}: ${length(local.names)} names\n"
}

A conditional requires a boolean condition and evaluates only the selected branch. Condition dependencies and sensitivity propagate into selected values. toset accepts a list or set, unifies compatible primitive elements, and deduplicates deterministically. length accepts a collection or string; string length counts Unicode grapheme clusters.

Use each.key/each.value and count.index only inside the appropriate expanded instance. Module outputs use module.name.output; path.module is the canonical declaring directory. All built-in functions and template directives are available. Final argument expansion is supported for ordinary functions, for example max([1, 2, 3]...); try and can take explicit expressions.