Skip to main content
RunBook Academy

AnsibleXXIX · Dynamic InventoryDynamic inventory foundations

Inventory plugins, not inventory scripts

Advanced⏱ ~22 minbash

What you'll learn

  • Explain what an inventory plugin gains over an executable inventory script
  • Describe how the auto plugin turns a plugin: key into a loaded plugin
  • Predict whether a given filename will be considered as a plugin config
  • Diagnose a plugin config that produced no hosts and no error

Prerequisites

Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11

Not yet marked complete on this device.

Everything you have written into an inventory file so far, somebody had to type. That works while the fleet is something a person maintains. It stops working the moment machines are created and destroyed by something other than a human editing YAML — an autoscaler, a provisioning pipeline, a colleague clicking a console.

Dynamic inventory is the answer to that: instead of a file listing hosts, a piece of code that asks a system which hosts exist, and answers in the shape Ansible expects.

The question this part keeps returning to is what that change costs. A static inventory is reviewable — it is a file, it has a diff, somebody approved it. A dynamic inventory is a query result, and query results change without anybody approving anything. That is the whole subject of this part, and this lesson is where the mechanism starts.

Two generations of the same idea

Ansible has had dynamic inventory for its entire life, in two forms.

Inventory scripts came first. You wrote an executable — any language — that printed JSON when called with --list, and printed one host’s variables when called with --host <name>. You pointed -i at the executable and Ansible ran it as a subprocess.

Inventory plugins came second, and are the supported form now. You write a YAML configuration file, not a program. The file names a plugin, the plugin runs inside the Ansible process, and the plugin — usually written by the provider’s collection maintainers, not by you — does the API work.

The script plugin still exists in core, which is how the old form keeps working: an executable inventory source is handled by a plugin that shells out to it.

Read-only / Safewhat ships in core
$ ansible-doc -t inventory -l
ansible.builtin.advanced_host_list Parses a 'host list' with ranges
ansible.builtin.auto               Loads and executes an inventory plugin s...
ansible.builtin.constructed        Uses Jinja2 to construct vars and groups...
ansible.builtin.generator          Uses Jinja2 to construct hosts and group...
ansible.builtin.host_list          Parses a 'host list' string
ansible.builtin.ini                Uses an Ansible INI file as inventory so...
ansible.builtin.script             Executes an inventory script that return...
ansible.builtin.toml               Uses a specific TOML file as an inventor...
ansible.builtin.yaml               Uses a specific YAML file as an inventor...

Notice what is not in that list: anything cloud-specific. No AWS, no Azure, no OpenStack, no Proxmox. Provider plugins live in collections you install separately, which is deliberate — core does not want to carry a release cadence tied to a dozen vendor APIs.

Why the change was worth making

The script generation had four problems, and every one of them is an operational problem rather than a stylistic one.

Problem with scriptsWhat plugins do instead
The script is a program in your repository. It is code you own, review, patch and get CVEs for.The plugin is a dependency you version; your artefact is a config file with no executable code in it.
Each --host call was a separate subprocess invocation, so a large fleet meant thousands of forks.The plugin returns everything in one pass, in-process.
No caching. Every run hit the API again, at full cost.Caching is a documented, uniform set of options across every plugin.
Every script invented its own configuration mechanism — environment variables, a sidecar INI, hardcoded values.Configuration is a YAML file with a documented schema, readable by ansible-doc.

The third row is the one that shows up in an incident. A run that queries a cloud API once per host, from a controller with fifty forks, is a self-inflicted rate-limit event; a plugin with a cache is not.

How a config file becomes a running plugin

This is the mechanism people find surprising, so it is worth stating plainly.

When you point -i at a YAML file, Ansible does not read it and decide what it is. It offers the file to each enabled inventory plugin in order, and asks each one “can you parse this?”. The first that says yes, wins.

Read-only / Safethe enabled list and its order
$ ansible-config list | sed -n '/^INVENTORY_ENABLED:/,/^INVENTORY_EXPORT:/p'
INVENTORY_ENABLED:
default:
- host_list
- script
- auto
- yaml
- ini
- toml
description: List of enabled inventory plugins, it also determines the order in
  which they are used.
env:
- name: ANSIBLE_INVENTORY_ENABLED
ini:
- key: enable_plugins
  section: inventory
name: Active Inventory plugins
type: list

auto is the interesting one. Its entire job is to look for a plugin: key at the root of a YAML file, and if it finds one, load that plugin and hand it the file. Its own documentation says so:

Read-only / Safethe auto plugin describes itself
$ ansible-doc -t inventory ansible.builtin.auto
  By enabling the 'auto' inventory plugin, any YAML inventory config
file with a 'plugin' key at its root will automatically cause the
named plugin to be loaded and executed with that config. This
effectively provides automatic enabling of all installed/accessible
inventory plugins.
To disable this behavior, remove 'auto' from the 'INVENTORY_ENABLED'
config element.

That last sentence is the practical consequence: because auto is enabled by default, you usually do not need to add your provider plugin to enable_plugins at all. Plenty of vendor documentation tells you to, and it does no harm, but the reason it works without that step is auto.

The naming rules that decide whether you are read

Two rules, and both of them bite quietly.

Rule one: a plugin config must end in .yml or .yaml. auto will not look at aws.conf, inventory.txt or a file with no extension. Here is a constructed config saved as con.txt, alongside a perfectly good static file:

Read-only / Safea plugin config with the wrong extension
$ ansible-inventory -i naming/00-static.yml -i naming/con.txt --graph
[WARNING]: Failed to parse inventory with 'ini' plugin: Failed to parse inventory:
Invalid host pattern 'plugin:' supplied, ending in ':' is not allowed, this
character is reserved to provide a port.
[WARNING]: Unable to parse .../naming/con.txt as an inventory source
@all:
|--@ungrouped:
|  |--web01.example.com
|  |--web02.example.com
|  |--db01.example.com
|  |--stage-web01.example.com

The hosts are there — from the static file — so the run proceeds. The grouping you wrote is gone. Nothing failed.

Rule two: in an inventory directory, files are read in lexicographic order, and some are skipped entirely. Extensions in INVENTORY_IGNORE_EXTS are never considered — the default list includes .cfg, .orig and .retry on top of the standard rejected extensions — and INVENTORY_IGNORE_PATTERNS lets you add regexes.

The ordering matters because later sources merge over earlier ones, which is why real repositories number them:

Read-only / Safea numbered inventory directory
inventory/
00-static-core.yml       # hosts that always exist, hand-maintained
10-cloud.yml             # plugin config: the provider API
20-constructed.yml       # plugin config: derived groups
90-overrides.yml         # static last word, including do_not_automate

Naming a plugin that is not installed

The commonest version of the above, because it happens on a fresh controller where somebody forgot the collection:

Read-only / Safethe collection is not installed
$ ansible-inventory -i naming/nope.yml --graph
[WARNING]: Failed to parse inventory with 'auto' plugin: inventory config
'.../naming/nope.yml' specifies unknown plugin 'amazon.aws.aws_ec2'
[WARNING]: Failed to parse inventory with 'yaml' plugin: Plugin configuration YAML
file, not YAML inventory
[WARNING]: Unable to parse .../naming/nope.yml as an inventory source
[WARNING]: No inventory was parsed, only implicit localhost is available

The fix is ansible-galaxy collection install amazon.aws, and the lesson is that the first warning line is the diagnosis. The three that follow are the loop continuing politely and are noise.

A five-step check for any new source

  1. Confirm the collection providing the plugin is installed: ansible-galaxy collection list, and look for the namespace you are about to name.
  2. Read the plugin documentation before writing the config: ansible-doc -t inventory <fqcn>. Every option and its default is there, including the caching options that come from a shared doc fragment.
  3. Name the file so auto will look at it: it must end in .yml or .yaml, and if it lives in an inventory directory, give it a number prefix that puts it where you intend in the merge order.
  4. Point ansible-inventory at it alone - not at the directory - and run --list. One source at a time is the only way to know which source produced what.
  5. Read the warnings, not just the output. A source that contributed nothing is a warning, and the hosts you see may all be coming from somewhere else.

Step four is the one worth insisting on. Once several sources are merged, “these hosts appeared” tells you nothing about which source produced them, and a source that silently contributes zero looks identical to one that is working.

Knowledge check

Knowledge check · 4 questions

  1. Q1. A colleague adds a new dynamic inventory source. The playbook run afterwards reports success, targets the same hosts it always did, and none of the new cloud instances appear. What should you check first?

  2. Q2. Why does a provider inventory plugin usually work without being added to enable_plugins?

  3. Q3. A YAML inventory file that has accidentally been made executable may be run as a program rather than parsed.

  4. Q4. Which of these are genuine operational advantages of an inventory plugin over the executable inventory script it replaces? Select all that apply.

Passing score: 75%. Answers are checked in this browser.