Developing Inventory Plugins
Relevant source files
The following files were used as context for generating this wiki page:
- lib/ansible/plugins/inventory/ini.py
- lib/ansible/plugins/inventory/constructed.py
- lib/ansible/plugins/inventory/yaml.py
- lib/ansible/plugins/inventory/generator.py
- lib/ansible/plugins/inventory/init.py
- lib/ansible/inventory/manager.py
- test/integration/targets/plugin_config_for_inventory/test_inventory.py
Inventory plugins are the primary mechanism for parsing inventory sources in Ansible. They allow Ansible to understand various data formats—from traditional INI and YAML files to dynamic cloud provider APIs—and translate them into a structured inventory of hosts and groups. Developing a custom inventory plugin enables integration with any system that can provide host information.
All inventory plugins must be able to be uniquely identified and are expected to populate the inventory object with hosts, groups, and variables. The core of Ansible's inventory system, the InventoryManager, iterates through enabled plugins to find one that can handle a given inventory source.
Core Concepts and Base Classes
Inventory plugins are Python classes that inherit from a set of base classes providing core functionality. The main classes are defined in lib/ansible/plugins/inventory/__init__.py.
Class Hierarchy
The fundamental building block for an inventory plugin is the BaseInventoryPlugin class. For plugins that read from files, BaseFileInventoryPlugin is a more specific base. Additionally, mixins like Cacheable and Constructable provide optional, advanced features.
This diagram shows the inheritance structure for inventory plugins.
Key Methods
Every inventory plugin must implement a few key methods to integrate with the inventory manager.
| Method | Description |
|---|---|
verify_file(path) | Checks if the plugin can handle the given inventory source path. It should return True if it can, and False otherwise. This is the first method called by the inventory manager to select a plugin. |
parse(inventory, loader, path, cache=True) | The main entry point for the plugin. It is responsible for reading the inventory source, parsing it, and populating the self.inventory object with hosts, groups, and variables. |
Sources: lib/ansible/plugins/inventory/init.py:100-117
A minimal inventory plugin must define a NAME attribute and an InventoryModule class inheriting from BaseInventoryPlugin.
# lib/ansible/plugins/inventory/yaml.py
from ansible.plugins.inventory import BaseFileInventoryPlugin
class InventoryModule(BaseFileInventoryPlugin):
NAME = 'yaml'
def verify_file(self, path):
# ... logic to check if file is a valid YAML inventory ...
return valid
def parse(self, inventory, loader, path, cache=True):
super(InventoryModule, self).parse(inventory, loader, path)
# ... logic to parse the file and populate inventory ...
Sources: lib/ansible/plugins/inventory/yaml.py:65-80
Plugin Lifecycle and Parsing
The InventoryManager is responsible for orchestrating the parsing of inventory sources. It discovers and utilizes inventory plugins in a specific sequence.
Parsing Flow
The following diagram illustrates the process InventoryManager uses to parse a single inventory source.
- Plugin Loading: The
InventoryManagerfirst fetches all enabled inventory plugins specified inC.INVENTORY_ENABLED. - Source Iteration: For each inventory source provided, the manager iterates through the loaded plugins.
- Verification: It calls the
verify_file(source)method on each plugin. The first plugin to returnTrueis selected to parse the source. - Parsing: The
parse(inventory, loader, source, cache)method of the selected plugin is executed. This method receives:inventory: AnInventoryDataobject to be populated.loader: ADataLoaderinstance for file and data handling.source: The path or string of the inventory source.cache: A boolean indicating if caching should be used.
- Population: Inside
parse, the plugin adds hosts, groups, and variables to the inventory object using methods likeself.inventory.add_host(),self.inventory.add_group(), andself.inventory.set_variable(). - Reconciliation: After all sources are parsed,
InventoryManagercallsreconcile_inventory()to finalize relationships and data.
Sources: lib/ansible/inventory/manager.py:246-379, lib/ansible/plugins/inventory/yaml.py:82-100
Plugin Structure and Metadata
For Ansible to recognize and document a plugin, it must contain specific static metadata variables.
DOCUMENTATION Block
The DOCUMENTATION string is a YAML-formatted block that describes the plugin, its options, and how to use it. This is used by ansible-doc to generate help text.
Key fields include:
| Field | Description |
|---|---|
name | The unique name of the plugin (e.g., yaml, constructed). |
version_added | The Ansible version in which the plugin was introduced. |
short_description | A one-line summary of the plugin's purpose. |
description | A more detailed explanation of the plugin's functionality. |
options | A dictionary defining the configuration options the plugin accepts. |
Sources: lib/ansible/plugins/inventory/constructed.py:7-39, lib/ansible/plugins/inventory/yaml.py:7-30, lib/ansible/plugins/inventory/generator.py:7-43
EXAMPLES Block
The EXAMPLES string provides usage examples, typically in the format of a configuration file or command, to help users understand how to use the plugin.
# lib/ansible/plugins/inventory/generator.py
EXAMPLES = """
# inventory.config file in YAML format
# remember to enable this inventory plugin in the ansible.cfg before using
# View the output using `ansible-inventory -i inventory.config --list`
plugin: ansible.builtin.generator
hosts:
name: "{{ operation }}_{{ application }}_{{ environment }}_runner"
parents:
- name: "{{ operation }}_{{ application }}_{{ environment }}"
# ...
"""
Sources: lib/ansible/plugins/inventory/generator.py:45-73
Handling Configuration
Plugins can be configured through a YAML file. The _read_config_data(path) helper method in BaseInventoryPlugin can be used to load, validate, and process this configuration.
Defining Options
Options are defined within the options key of the DOCUMENTATION block. Each option is a dictionary specifying its description, type, default value, and environment/INI configuration mappings.
# lib/ansible/plugins/inventory/constructed.py
DOCUMENTATION = """
# ...
options:
plugin:
description: token that ensures this is a source file for the 'constructed' plugin.
required: True
choices: ['ansible.builtin.constructed', 'constructed']
use_vars_plugins:
description:
- Normally, for performance reasons, vars plugins get executed after the inventory sources complete the base inventory...
required: false
default: false
type: boolean
version_added: '2.11'
# ...
"""
Sources: lib/ansible/plugins/inventory/constructed.py:14-31
Accessing Options
Within the plugin's methods, options can be retrieved using self.get_option('option_name'). This is typically done after calling self.set_options() or self._read_config_data().
# test/integration/targets/plugin_config_for_inventory/test_inventory.py
class InventoryModule(BaseInventoryPlugin):
# ...
def parse(self, inventory, loader, path, cache=True):
super(InventoryModule, self).parse(inventory, loader, path)
config_data = self._read_config_data(path=path)
self._consume_options(config_data)
departments = self.get_option('departments')
# ...
Sources: test/integration/targets/plugin_config_for_inventory/test_inventory.py:43-49
Advanced Features
Caching with Cacheable
The Cacheable mixin provides an interface for inventory plugins to cache their results, which is particularly useful for dynamic inventories that query slow external APIs.
To implement caching:
- Inherit from
Cacheable. - Call
self.load_cache_plugin()in theparsemethod. - Use
self.get_cache_key(path)to generate a unique key for the inventory source. - Read from and write to the cache using
self._cache[cache_key].
Sources: lib/ansible/plugins/inventory/init.py:172-200
Dynamic Construction with Constructable
The Constructable mixin provides helper methods for creating groups and variables based on Jinja2 expressions evaluated against existing host variables. This is the foundation for plugins like constructed.
Key methods provided by Constructable:
_set_composite_vars(): Creates new host variables based on Jinja2 expressions._add_host_to_composed_groups(): Adds hosts to groups based on conditional Jinja2 expressions._add_host_to_keyed_groups(): Creates groups based on the value of a specific variable (e.g., creating groups for each OS distribution).
Sources: lib/ansible/plugins/inventory/init.py:203-345, lib/ansible/plugins/inventory/constructed.py
Templating with Templar
Plugins have access to a Templar instance via self.templar. This allows for powerful Jinja2 templating to generate hostnames, group names, and variable values dynamically, as seen in the generator plugin.
# lib/ansible/plugins/inventory/generator.py
class InventoryModule(BaseInventoryPlugin):
# ...
def template(self, pattern, variables):
# ...
return self.templar.copy_with_new_env(available_variables=variables).template(pattern)
def parse(self, inventory, loader, path, cache=False):
# ...
for item in template_inputs:
# ...
host = self.template(config['hosts']['name'], template_vars)
inventory.add_host(host)
self.add_parents(inventory, host, config['hosts'].get('parents', []), template_vars)
Sources: lib/ansible/plugins/inventory/generator.py:100-120
Summary
Developing an Ansible inventory plugin involves creating a Python class that inherits from BaseInventoryPlugin or BaseFileInventoryPlugin. The plugin must implement verify_file to identify compatible inventory sources and parse to process the source and populate the inventory object. Essential metadata, including DOCUMENTATION and EXAMPLES, must be provided for ansible-doc integration. For more complex scenarios, mixins like Cacheable and Constructable offer powerful features for caching and dynamic inventory generation.