Skip to navigation

Facts and custom collectors

rivet facts prints a JSON snapshot of a host with three parts:

  • target: distribution, version, architecture, init system, hashed machine identity, and capabilities. Compilation and apply use these to check the bundle fits the host.
  • collected_at_ms: when the snapshot was taken.
  • facts: inventory, described below, which configuration reads as facts.<group>....
rivet facts --local > facts.json # on the host
rivet facts --target app-1 > app-1.json # over SSH

Collection only reads. Groups whose sources aren’t available are left out, as are individual fields that can’t be read. A failure to read the OS or machine identity is an error. Older snapshots without a facts section, and bare target files like examples/file/target.json, still work.

Built-in facts

GroupContents
cloudprovider and normalized attributes: instance ID, region, availability zone, hostname, and instance type/image/account/project identifiers when present.
disksWhole block devices keyed by kernel name, with byte size, model, vendor, serial, WWN, and rotational type when available.
identityHashed machine_id; effective caller UID/GID, local passwd and group names, and privileged.
memorySystem and swap total_bytes, available_bytes, and used_bytes.
mountsMountpoints keyed by decoded path, with device, filesystem, options, and space in bytes when statvfs succeeds.
networkingHostname, domain/FQDN when the kernel hostname includes a domain, interface links, IPv4/IPv6 bindings, and the lowest-metric active IPv4 default interface.
osDistribution ID, version, name, full name, architecture, init system, and kernel name/release.
partitionsDevice paths with partition number, disk when identifiable, byte size and offset, and available filesystem/UUID/label information.
processorsLogical CPU count and models, physical socket and core counts when reported, architecture, and online CPU range. Counts describe the host, not cgroup quota.
gpusPCI display controllers, including unloaded devices, keyed by PCI address; class, device and vendor IDs, known vendor name, and driver. NVIDIA devices also include model name, UUID, NVML index, driver version, architecture, and available telemetry under metrics.
securityObserved selinux.mode (enforcing/permissive/disabled), apparmor.enabled, secure_boot.enabled, and fips.enabled. Missing or unreadable signals are omitted, not reported as disabled.
numanodes keyed by node ID, with CPU arrays, total memory_bytes, distances keyed by destination node ID, and local gpus (PCI addresses) / network_interfaces when identifiable.
dmiManufacturer, BIOS release date/vendor/version, board asset tag/manufacturer/product/serial number, chassis asset tag and type, and product name/version/serial number/UUID. Chassis type is the sysfs numeric code as a string. Permission-restricted fields are omitted.
virtualizationis_virtual, plus detected hypervisor and container names. VM and container layers are queried independently; unknown detection is omitted.
uptimeWhole seconds, hours, and days since boot, formatted uptime, and optional boot_id.

These groups follow Puppet’s structured fact organization, using virtualization and uptime as group names. NUMA uses the kernel’s node sysfs interface; it describes host topology rather than process affinity or cgroup limits. Security reads runtime kernel controls and the EFI SecureBoot variable (after the efivarfs attribute prefix). Virtualization uses systemd-detect-virt --vm / --container, with hypervisor and container marker files as fallbacks. Both detection commands must report no virtualization before is_virtual is false.

Cloud facts come from the instance metadata service at 169.254.169.254: AWS IMDSv2, Azure IMDS, or GCP metadata. Requests skip proxies, time out after one second, and the whole cloud lookup is limited to five seconds. Only inventory fields are collected, never credentials or user data.

The public /run/cloud-init/instance-data.json cache is a fallback for unavailable API fields or providers without a native collector, such as OpenStack. API values take precedence, and cached fields are merged only for the same provider. AWS/GCP/Azure also have DMI detection when metadata is unavailable; attributes can then be empty. The sensitive cloud-init cache is never read. See cloud-init’s standardized instance data.

Networking uses sysfs for links and the installed ip command for address bindings; without iproute2, link facts still appear and address fields are omitted. Disk/filesystem identifiers come from sysfs, the optional udev database, and mounted filesystem records.

NVIDIA telemetry is read through the installed NVIDIA driver library; nvidia-smi isn’t needed. Without the driver library, GPUs still appear with their PCI and driver details, and readings that are unsupported or not permitted are left out. AMD and Intel GPUs currently show PCI and driver details only.

NVIDIA metrics follows the names in AWS’s GPU metric reference:

FieldsUnits
utilization_gpu, utilization_memory, fan_speedPercent; memory utilization measures activity, not allocated capacity. Fan speed uses fan 0.
temperature_gpuCelsius
power_drawWatts
memory_total, memory_used, memory_freeMiB, matching NVIDIA memory reporting; exact memory_total_bytes, memory_used_bytes, and memory_free_bytes are also included.
pcie_link_gen_current, pcie_link_width_currentGeneration and lane count
encoder_stats_session_count, encoder_stats_average_fps, encoder_stats_average_latencySessions, frames per second, microseconds
clocks_current_graphics, clocks_current_sm, clocks_current_memory, clocks_current_videoMHz

GPU telemetry describes the collection instant, like available memory and mount space; it is not an apply-time target constraint.

Use facts in HCL

Compile against a snapshot:

rivet facts --local > facts.json
rivet compile ./site --facts facts.json --out host.bundle

Nested paths, literal string keys, and literal array indices work:

file "host_summary" {
path = "/etc/host-summary"
content = "${facts.os.name}: ${facts.processors.count} CPUs (${facts.processors.models[0]})"
}
output "root_filesystem" {
type = string
value = facts.mounts["/"].filesystem
}
output "cpu_count" {
type = number
value = facts.processors.count
}

facts.distribution, facts.version, facts.architecture, and facts.init_system are also available, including when compiling against a bare target file. Fact values keep their JSON types: whole numbers become integers, and other numbers stay fractional. Paths can use computed indices, splats, and for expressions, and integer facts can drive count. A missing fact or invalid index is a compile error.

Only facts that configuration actually uses affect the catalog digest. Values such as free memory, mount usage, and network addresses describe the moment of collection and aren’t rechecked during apply, so prefer stable facts when making decisions.

Custom JSON facts

Add your own facts by putting files in /etc/rivet/facts.d, or another directory passed with --facts-dir DIR:

mkdir -p ./facts.d
printf '%s\n' '{"variety":"russet","count":3}' > ./facts.d/potato.rb
rivet facts --local --facts-dir ./facts.d > facts.json

The final extension is removed: potato.rb, potato.json, or potato supplies facts.potato. An executable file is executed directly (use a valid shebang); its stdout must contain one complete JSON value. A non-executable file is read as JSON regardless of extension. Objects, arrays, scalars, booleans, and null retain their types. The example exposes facts.potato.variety and facts.potato.count.

Files are processed in sorted filename order, non-recursively. Hidden files and directories are skipped; symlinks to regular files are followed. A missing directory supplies no custom facts. Duplicate basenames and collisions with any built-in fact or the four legacy aliases fail before custom executables run. Non-UTF-8 basenames, invalid JSON, unreadable files, failed execution, and nonzero exits fail collection without emitting a partial snapshot or exposing JSON/stderr in the error.

Executables run as the invoking user, with the facts directory as their working directory, stdin closed, and stderr discarded. Each executable has a ten-second budget; timeout/output-limit failures terminate its process group. JSON from each file and combined custom input is bounded to 1 MiB; the complete serialized snapshot must also fit the compiler’s 1 MiB input limit. Scripts must keep stdout free of diagnostic messages.

Custom executables run during rivet facts and each converging run, including noop/offline. Compile/validate and run --prepare-only --facts consume the saved snapshot. SSH compatibility probes and direct plan and apply collect only target identity and capability data, without full inventory collection or custom executables.

Inventory and verified identity

Facts are reported by the host itself, so they can’t prove who a host is. Cloud metadata and custom facts can be forged by anyone with root on the host. To classify hosts by verified identity, use the compile service and read trusted claims instead.