Data, Facts, and Templates

Three cooperating packages feed every run. facts gathers what is true about the host. hiera layers a hierarchy of data blocks into one resolved map, driven by those facts. templates renders that data into resource fields. The manager wires them together and hands the result to the apply engine.

Where it lives

facts: system fact collectors backed by gopsutil, plus file-based facts. hiera: the hierarchy resolver and its comment-driven validation. templates: the render environment and the expression engines. Key files: facts/facts.go, hiera/resolver.go, hiera/validate.go, templates/templates.go, templates/expr.go, templates/jet.go.

Facts

facts.Gather (facts/facts.go:17) builds a map from the built-in families, host, network, partition, cpu, and memory, each backed by gopsutil and each skippable with a config flag. It then merges file-based facts on top: for the system config directory and then the user directory, it reads facts.json, facts.yaml, and a sorted facts.d/ directory, deep-merging each in order so later sources win.

Load-bearing decision

File facts refuse symlinks and require absolute config directories. Symlinked fact files and directories are rejected outright. This is a security invariant: fact data drives what gets installed and where, so it must not be redirectable through a planted symlink.

Runtime caching lives in the manager, not the facts package. Facts() gathers once and memoizes; SystemFacts() always re-gathers with a 2-second default deadline; SetFacts and MergeFacts let callers override or overlay, which is how the agent reuses its last good facts when a gather fails.

Hiera

Factsgopsutil + facts.dHierarchy + overrideslevels templated by factsMergefirst 路 deepResolved data+ validateTemplate envlookup 路 kvGet 路 registrationsResource fields{{ }} 路 ${ } 路 jet
Facts drive the hierarchy; the merged data and facts feed the template environment that renders resource fields.

Resolve (hiera/resolver.go:98) starts from the data: block as the base, then walks hierarchy.order. Each level is a template like os:{{ lookup('facts.host.info.platformFamily') }}. A level counts as matched only when its embedded expression produces a non-empty value, so a level whose fact is missing is skipped. The matched string is looked up in overrides: and merged into the base.

merge: first
The default. The first matching level wins; its top-level keys replace the base, and resolution returns immediately.
merge: deep
Every matching level accumulates. Maps merge recursively and slices concatenate, in hierarchy order.
Sources
ResolveUrl dispatches by scheme: a local YAML or JSON file, an http(s) URL with basic auth, or a NATS JetStream KV document at kv://Bucket/Key.
Validation
YAML comments @require and @validate <expr> become rules. Resolution returns the rules so a multi-source caller validates once after merging.
Load-bearing decision

Hiera resolution runs in a restricted sandbox. During resolution only facts and lookup() are available, with missing keys returning empty rather than erroring. File, KV, and registration functions are excluded. The full function set only exists later, at render time, in the manager-built environment. This keeps hierarchy selection from reaching into the network.

Templates

{{ expr }} and ${ expr } are aliases. Both route through the same expr-lang evaluator, so ${ Data.package_name } and {{ Data.package_name }} are identical. A separate jet(...) renderer handles Jet templates with [[ ]] delimiters, used mainly to generate resource lists.

Field access
Inside an expression, use Go field names: Data.app_name, Facts.os, Environ.X.
lookup(key, default)
Takes a lowercased, dotted path rooted at the environment: lookup('data.nested.key'), lookup('facts.env'). It marshals the environment to JSON and queries it with gjson.
Type preservation
When the whole string is a single expression, its native typed value is returned, so a templated port stays an integer. Mixed strings are stringified and concatenated.
Deferred fields
Struct fields tagged template:"deferred", such as file content, render in a second pass after the control gate, so an unless can protect a resource from a template error.
Load-bearing decision

kvGet(bucket, key) and the kv://Bucket/Key Hiera source are different mechanisms and are easy to conflate. kvGet is an in-template lookup of a single KV value at render time. kv:// is a whole-document Hiera data source resolved during data resolution.

One syntax is reserved but not implemented. The Resolve doc comment and a test reference Puppet-style env:%{env} placeholders, but only {{ }} and ${ } are recognized. A %{...} entry is treated as a literal key with no matched expression, so it is skipped. Real hierarchies use env:{{ lookup('facts.env') }}.

Next

Continue to Registration and Discovery to see how resolved resources announce themselves for others to find.