Summary
A senior engineer diagnosed an Ansible inventory parsing failure when moving from a shell-expanded command-line path (-i /Users/myuser/inventory/*.ini) to a static ansible.cfg configuration. The root cause was using a shell glob pattern (*.ini) inside the configuration file, which Ansible interprets literally rather than expanding. The fix is to use the directory path instead of a file pattern or to configure multiple inventory entries.
Root Cause
The core issue is the mismatch between how shells (bash, zsh) handle wildcards and how Ansible parses configuration values.
- Command Line Success: When you run
ansible-inventory -i /Users/myuser/inventory/*.ini, the shell expands the wildcard*into a list of space-separated files before passing arguments to the Ansible process. Ansible receives a list of valid file paths and processes them successfully. - Configuration File Failure: In
ansible.cfg, the valueinventory=/Users/myuser/inventory/*.iniis treated as a single string literal. Ansible attempts to open a file literally named*.ini(or treats the asterisk as a syntax error depending on the plugin), which does not exist, resulting in the[WARNING]: Unable to parse ...error.
Why This Happens in Real Systems
This behavior occurs because ansible.cfg is not a shell script; it is a static INI-formatted file parsed by Python’s configparser.
- Static Parsing: Ansible reads the config file directly. It does not invoke a shell to expand variables or wildcards.
- Plugin Specificity: The
iniinventory plugin expects either a specific file path or a directory. It does not support glob patterns defined in the configuration string. While theyamlandtomlplugins have some support for globbing in explicitinventory.hostssettings, the standardinventory =directive in[defaults]is strictly path-based.
Real-World Impact
- Inventory Invisibility: Hosts and groups defined in the
.inifiles are completely ignored by Ansible. - Playbook Failures: Playbooks targeting
allor specific groups defined in those files will fail with “host not found” errors or default to empty runs. - Debugging Overhead: Engineers waste time checking file permissions and syntax, overlooking the fundamental issue of path resolution.
- CI/CD Pipeline Breakage: Automated scripts that rely on the config file will fail if they don’t explicitly override the inventory path via the command line (
-i).
Example or Code
Here is the comparison of the failing configuration and the working solutions.
Failing ansible.cfg (Using Shell Glob):
[defaults]
inventory=/Users/myuser/inventory/*.ini
[inventory]
enable_plugins=host_list, script, auto, yaml, ini, toml
Working Solution A: Use a Directory (Recommended)
If you have multiple inventory files, place them in a directory and point to the directory. Ansible will automatically load all valid inventory files within it.
[defaults]
inventory=/Users/myuser/inventory/
[inventory]
enable_plugins=host_list, script, auto, yaml, ini, toml
Working Solution B: Multiple Inventory Entries
If you must list specific files, you can define the key multiple times (Ansible 2.9+ supports multi-value keys in some contexts, but strictly speaking, comma-separated lists are often safer, or rely on multiple files if the parser allows).
[defaults]
inventory=/Users/myuser/inventory/group_vars.ini, /Users/myuser/inventory/host_vars.ini
How Senior Engineers Fix It
Senior engineers address this by aligning the configuration method with Ansible’s parsing logic:
- Switch to Directory Mode: The standard practice for managing complex inventories is to use a directory structure (e.g.,
inventory/) containing various.ini,.yml, or.cfgfiles. Pointinginventory=to this directory ensures Ansible loads all of them without manual globbing. - Use Explicit Paths: If using a directory isn’t an option, explicitly list the files separated by commas (if the parser supports it) or create a wrapper script that generates a consolidated inventory file if a specific order is required.
- Verification: Always verify the parsed inventory using
ansible-inventory --graph -i <path>to ensure the structure matches expectations before running playbooks.
Why Juniors Miss It
- Shell Habit Transfer: Developers often try to apply shell syntax (wildcards,
$HOME, pipes) directly into configuration files, assuming the config parser is as “smart” as a shell terminal. - Confusion with CLI: Since the exact same string works on the command line, they assume the config file is buggy or corrupted, rather than understanding the context difference (shell expansion vs. static parsing).
- Documentation Skimming: The Ansible documentation explicitly mentions using directories for inventory, but this is easily missed when looking for a quick “one-liner” configuration fix.
- Lack of Debugging Flags: Juniors might not immediately use
ansible-inventory -v(verbose) to see exactly which file path Ansible is attempting to load, leading to prolonged guesswork.