Parse YAML inventory files with ansible-inventory - #6
Conversation
YAML is Ansible's inventory format of choice these days, but a '.yml' or '.yaml' inventory was handed to the ini parser, which does not fail -- it silently invents hosts out of the YAML structure. Given a normal YAML inventory it produced hosts named 'all', 'children', 'hosts', 'vars', 'dtap' and 'comment' alongside the real ones, with host variables attached to the wrong entries. Detect '.yml'/'.yaml' inventory files and shell out to 'ansible-inventory -i <file> --list', which emits the same JSON structure as a dynamic inventory script, so the existing DynInvParser consumes it unchanged. If 'ansible-inventory' is not on PATH, report that Ansible needs to be installed rather than surfacing a bare OSError. A non-zero exit relays ansible-inventory's stderr and skips the inventory instead of feeding the parser empty output. Adds a test with a YAML inventory fixture, asserting both that real hosts and their vars are picked up and that YAML keywords do not leak in as hosts. It skips when ansible-inventory is unavailable. Based on fboender#204 by arnisoph. Reworked: the original added a bare 'if' ahead of the existing 'elif os.path.isfile(...)' branch, which made executable inventories match both the dynamic-script branch and the static-file branch and be parsed twice. This uses an elif so the chain stays exclusive.
There was a problem hiding this comment.
Pull request overview
This PR adds first-class support for YAML (.yml/.yaml) Ansible inventory files by dispatching them to ansible-inventory --list and then reusing the existing dynamic-inventory JSON parsing path, preventing YAML keywords from being misinterpreted as hosts by the INI parser.
Changes:
- Route
.yml/.yamlinventory files throughansible-inventory --listand ingest its JSON output. - Add a YAML inventory fixture and a unit test to ensure real hosts/vars are parsed and YAML structural keywords do not appear as hosts.
- Improve error handling around invoking
ansible-inventory(missing executable / non-zero exit).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/ansiblecmdb/ansible.py |
Adds YAML inventory detection and a parser that shells out to ansible-inventory and feeds its JSON into DynInvParser. |
test/test.py |
Adds a skip-guarded unit test validating YAML inventory parsing behavior. |
test/f_inventory/inventory.yml |
Adds a YAML inventory fixture used by the new test. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- DynInvParser ignored the 'children' key, so a host listed only in a
leaf group reported just that group. A host under all -> infra ->
dbservers came out with groups {'dbservers'} instead of also including
'infra'. Collect the hierarchy while parsing and resolve it once all
groups are known, with cycle protection for malformed inventories.
Note group *vars* were already correct: 'ansible-inventory --list'
resolves inherited vars into _meta.hostvars, so they never depended on
this traversal.
- Match '.yml'/'.yaml' case-insensitively. Ansible's own inventory
plugins only match lowercase, so an 'INVENTORY.YML' still won't parse
-- but it now fails with an explicit ansible-inventory error instead
of being handed to the ini parser, which fabricated hosts out of the
YAML structure.
- Drop the duplicate 'import sys' in the test module.
Adds tests for nested group membership and the uppercase extension, and
extends the YAML fixture with a host nested two levels deep.
|
Merge note: conflicts with #10 in
The resolution that works is to factor the shared path into a Clean against every other open PR. |
Based on the stalled fboender/ansible-cmdb#204 by
arnisoph(refs fboender#154), reworked to fix a control-flow bug in the original.The bug
YAML is Ansible's inventory format of choice these days, but a
.yml/.yamlinventory was handed to the ini parser. It doesn't fail — it silently invents hosts out of the YAML structure.Given an ordinary YAML inventory, the parsed host list was:
Eight fabricated hosts from YAML keywords, with host variables attached to the wrong entries. After this change:
The fix
Detect
.yml/.yamland shell out toansible-inventory -i <file> --list, which emits the same JSON structure as a dynamic inventory script — so the existingDynInvParserconsumes it unchanged.Error handling is a little firmer than the original: if
ansible-inventoryisn't onPATHwe say Ansible needs installing rather than surfacing a bareOSError, and a non-zero exit relays its stderr and skips the inventory instead of feeding the parser empty output.Reworked from the original
The upstream patch inserted a bare
ifahead of the existingelif os.path.isfile(...):That detaches the
eliffrom the executable check, so an executable non-YAML inventory matches the dynamic-script branch and then the static-file branch, and gets parsed twice. This version useselifso the chain stays exclusive.I also dropped the original's
proc.communicate(input), which passed the builtininputfunction.Testing
Adds
testYamlInventorywith a YAML fixture, asserting both that real hosts and their vars are picked up and that YAML keywords don't leak in as hosts. It skips cleanly whenansible-inventoryis unavailable, so it won't break environments without Ansible.Full suite 10/10 pass. Regression-checked ini, directory, and executable dynamic inventories to confirm the dispatch chain still routes each correctly.