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
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
ResolveUrldispatches by scheme: a local YAML or JSON file, anhttp(s)URL with basic auth, or a NATS JetStream KV document atkv://Bucket/Key.- Validation
- YAML comments
@requireand@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 filecontent, render in a second pass after the control gate, so anunlesscan 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.