We've seen plugins that fetch hosts from a source (AWS, Azure, scripts). Two built-in plugins do something different — they transform existing inventory. Drop them into the same inventory directory and they layer on top.
The constructed plugin reads each host's variables (or facts gathered earlier in the
run) and builds new groups based on Jinja2 expressions. Useful when your hosts come
from a flat source but you need them grouped by some derived attribute.
---
plugin: ansible.builtin.constructed
strict: false
# 1. KEYED GROUPS — auto-create groups for each unique value
keyed_groups:
# Group hosts by OS family (creates os_family_RedHat, os_family_Debian, …)
- prefix: os_family
key: ansible_os_family
# Group hosts by datacenter (creates dc_mumbai, dc_singapore, …)
- prefix: dc
key: datacenter | default("unknown")
# Group EC2 instances by instance type
- prefix: instance
key: ec2_instance_type
# 2. GROUPS — boolean expressions, hosts join if expr is true
groups:
large_hosts: ansible_memtotal_mb >= 16000
oel8_hosts: ansible_distribution == "OracleLinux" and ansible_distribution_major_version == "8"
primaries: mysql_role | default("") == "primary"
# 3. COMPOSE — derived per-host variables
compose:
is_oel8: 'ansible_distribution == "OracleLinux"'
short_hostname: 'inventory_hostname.split(".")[0]'
# Combined inventory: AWS EC2 + constructed transforms
ansible-inventory -i inventories/aws_ec2.yml -i inventories/constructed.yml --graph
# Sample output
# @all:
# |--@aws_ec2:
# |--@os_family_RedHat:
# | |--ip-10-0-1-12.ap-south-1.compute.internal
# |--@dc_mumbai:
# | |--ip-10-0-1-12.ap-south-1.compute.internal
# |--@large_hosts:
# | |--ip-10-0-1-12.ap-south-1.compute.internal
# |--@primaries:
# | |--db1.example.com
The generator plugin produces inventory by combining a template with a list of
values. Useful for things like "I have 30 numbered worker hosts in N regions":
---
plugin: ansible.builtin.generator
hosts:
name: "worker-{{{{ region }}}}-{{{{ shard }}}}"
parents:
- name: "region_{{{{ region }}}}"
parents:
- name: workers
- name: "shard_{{{{ shard }}}}"
layers:
region:
- mumbai
- singapore
- frankfurt
shard:
- "01"
- "02"
- "03"
That config generates 9 hosts (3 regions × 3 shards), all in workers, plus per-region and per-shard groups:
ansible-inventory -i inventories/generator.yml --graph
# @all:
# |--@workers:
# | |--worker-mumbai-01
# | |--worker-mumbai-02
# | |--worker-mumbai-03
# | |--worker-singapore-01
# | |--worker-singapore-02
# | |--worker-singapore-03
# | |--worker-frankfurt-01
# | |--worker-frankfurt-02
# | |--worker-frankfurt-03
# |--@region_mumbai:
# |--@region_singapore:
# |--@region_frankfurt:
# |--@shard_01:
# |--@shard_02:
# |--@shard_03:
generator shines for synthetic / sharded fleets and labs — all the database labs in this series use it to produce numbered nodes for cluster setups.The real power of inventory plugins is composition. Drop several plugin configs into the same directory and Ansible processes each, merging the result.
inventories/production/
├── 01-static.ini # static bastion + control plane
├── 02-aws_ec2.yml # AWS-discovered hosts
├── 03-constructed.yml # derive groups from EC2 tags + facts
└── group_vars/
├── all.yml
├── os_family_RedHat.yml # auto-applied to RHEL-family hosts
└── primaries.yml # auto-applied to anything in 'primaries'
Plugins run in alphabetical order, so the numeric prefixes guarantee that static sources are read first, dynamic ones second, and transforms last. The final inventory is the union.
# Show every inventory plugin Ansible can find
ansible-doc -t inventory -l
# Output (excerpt):
# amazon.aws.aws_ec2 - EC2 inventory source
# amazon.aws.aws_rds - rds instances inventory
# ansible.builtin.constructed - Use Jinja2 to construct groups
# ansible.builtin.generator - Generates an inventory from a definition
# ansible.builtin.host_list - Parses host list from CLI
# ansible.builtin.ini - Uses INI file
# ansible.builtin.script - Executes inventory script
# ansible.builtin.toml - Uses TOML file
# ansible.builtin.yaml - Uses YAML file
# google.cloud.gcp_compute - Google Cloud Compute Engine
# kubernetes.core.k8s - Kubernetes inventory source
# Detailed docs for one plugin
ansible-doc -t inventory ansible.builtin.constructed
Built-in plugins (constructed, generator, ini, yaml, script) are always available. Plugins
from collections need to be explicitly enabled in ansible.cfg:
[inventory]
enable_plugins = ini, yaml, constructed, generator,
amazon.aws.aws_ec2,
google.cloud.gcp_compute,
kubernetes.core.k8s
aws_ec2.yml isn't being picked up, check enable_plugins first. Forgetting this is the #1 reason a perfectly-written plugin config silently produces no hosts.