inventory — INI/YAML inventory
inventory
github.com/go-ansible/inventory parses Ansible-compatible inventories —
both YAML and INI — into the group/host graph, including
group_vars/host_vars directories, and matches Ansible’s host-pattern
syntax against it.
Data model
type Host struct {
Name string
Vars map[string]any
}
type Group struct {
Name string
Hosts map[string]*Host
Children map[string]*Group
Parents map[string]*Group
Vars map[string]any
}
type Inventory struct {
Hosts map[string]*Host
Groups map[string]*Group
}New() returns an inventory pre-seeded with the two groups Ansible always
has: all (every host) and ungrouped (every host in no other group) —
computed the way Ansible computes it: a host only reaches ungrouped (and
all) if it belongs to no group other than those two.
Loading
func Load(path string) (*Inventory, error)path may be a single YAML file (.yml/.yaml), a single INI file (any
other extension, or none), or a directory — in which case every regular file
directly inside it (except group_vars/ and host_vars/) is parsed and
merged in name order, and then group_vars/<name>.yml (or a
group_vars/<name>/*.yml directory) and the equivalent host_vars/ siblings
are merged in, group vars before host vars, matching Ansible’s precedence.
Load returns ErrNoSources for a path that exists but holds no inventory
source — a directory whose entries are all subdirectories, group_vars/
host_vars, or dotfiles. Real refuses to parse such a directory rather than
treating it as an empty inventory, and a caller reporting to a user needs to
tell it apart from a parse failure: real names the directory but gives no
cause for it, never having reached a parser.
Dynamic inventory scripts
Load detects an executable path (IsScript, checking the file mode’s
executable bit) and runs it as a dynamic inventory script instead of parsing
it as YAML/INI, against the real script protocol: path --list for the full
group/host graph, path --host <name> for one host’s vars when a group
entry doesn’t already provide them via the _meta.hostvars optimization.
Each group in --list’s output may be either the shorthand array of
hostnames or the full {hosts, vars, children} object form — both parse.
group_vars/host_vars directories are not consulted for a script-backed
inventory, matching real Ansible.
Group ancestry and merge order
func (inv *Inventory) GroupsForHost(hostName string) []*GroupReturns every group a host belongs to, directly or through a parent group,
ordered all first and the most specific group last — the order Ansible
merges group vars in, so that a child group’s value for a key overrides its
parent’s.
Ordering
A Go map has no order, so the inventory keeps the orders that are observable
in real’s output separately: the order hosts were first seen (HostIndex,
which is what order: inventory means and what Match sorts by), the order
groups were created, and the order each group gained its children
(Group.ChildNames).
They are not cosmetic. ansible-inventory --list reports a group’s children
in document order — "prod": {"children": ["web", "db"]} for a
[prod:children] section written web-then-db — and "all" lists ungrouped
first because it exists from the start. ChildNames returns a copy, so a
caller sorting the result cannot reorder the inventory itself.
all is a parent of ungrouped from the moment New returns, not only once
an inventory has been parsed: an inventory that parsed nothing still has that
pair in real.
Host-pattern matching
func (inv *Inventory) Match(pattern string) ([]*Host, error)Resolves Ansible’s host-pattern language: all, a literal group or host
name, a glob, a numeric or alphabetic range (web[01:50]), and
colon/comma-separated combinations with ! exclusion and & intersection —
e.g. webservers:!web3:&datacenter1 reads as “every host in webservers
that is also in datacenter1, except web3.” Results are sorted by name for
deterministic output.
MatchReport returns the same hosts and, additionally, each term of the
pattern that matched nothing:
func (inv *Inventory) MatchReport(pattern string) ([]*Host, []string, error)Real Ansible warns once per such term — Could not match supplied host pattern, ignoring: zzz — which is what tells a mistyped pattern apart from
one that legitimately selects nothing. A term is reported when it produced no
hosts and matched no group and is not all; the group half matters,
because a term naming an empty group matches nothing yet is not a typo, and
real says nothing for it. Terms come back without their !/& operator and
in real’s evaluation order, so all:!zzz reports zzz.
What this does not do
inventory only builds and queries the graph — it does not itself decide
variable precedence between a host’s group vars, its own vars, and anything
from a play or the command line. That ladder lives in
vars, which consumes inventory’s output as one of its layers.