Include in ansible.cfg an entire directory as inventory path

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 value inventory=/Users/myuser/inventory/*.ini is 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 ini inventory plugin expects either a specific file path or a directory. It does not support glob patterns defined in the configuration string. While the yaml and toml plugins have some support for globbing in explicit inventory.hosts settings, the standard inventory = directive in [defaults] is strictly path-based.

Real-World Impact

  • Inventory Invisibility: Hosts and groups defined in the .ini files are completely ignored by Ansible.
  • Playbook Failures: Playbooks targeting all or 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:

  1. 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 .cfg files. Pointing inventory= to this directory ensures Ansible loads all of them without manual globbing.
  2. 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.
  3. 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.