Ansible dynamic inventory
Introduction
Dynamic inventory is a powerful Ansible feature that discovers the hosts to manage from an external source instead of a static file. The community.general.xen_orchestra inventory plugin connects to your Xen Orchestra instance and builds an inventory from the virtual machines (VMs) it manages, so your automation always targets the real state of your infrastructure.
With dynamic inventory, there is no need to manually maintain static inventory files. Your VMs are automatically discovered and classified into groups based on their properties.
Prerequisites
- a running Xen Orchestra instance connected to an XCP-ng pool
- Ansible with the
community.generalcollection, version 4.1.0 or newer (the version that introduced the plugin) - the
websocket-clientPython library, version 1.0.0 or newer (the plugin talks to the XO JSON-RPC API over a websocket) - an XO user account: this plugin authenticates with a username and password (it does not support API tokens)
# ansible-galaxy collection install community.general # pip install "websocket-client>=1.0.0" # ansible-doc -t inventory community.general.xen_orchestra
Configuring the inventory
Minimal configuration
Create a configuration file for the dynamic inventory, for example my.xen_orchestra.yml:
# my.xen_orchestra.yml
plugin: community.general.xen_orchestra
api_host: your-xo-hostname
user: your_username
password: your_password
use_ssl: true # connect over wss:// (set to false for plain ws://)
validate_certs: true # set to false if XO uses a self-signed certificate
The file name must end with .xen_orchestra.yml or .xen_orchestra.yaml for the plugin to load it.
api_host takes a hostname or IP address, without a ws:// or wss:// prefix: the plugin builds the websocket URL itself from use_ssl. The connection settings can also come from the ANSIBLE_XO_HOST, ANSIBLE_XO_USER, and ANSIBLE_XO_PASSWORD environment variables.
Securing credentials
Never keep plain-text credentials in a file that reaches version control. In production, store them with Ansible Vault or pass them through the environment variables above.
Create an encrypted file for your secrets:
# ansible-vault create vault.yml
with the following content:
# vault.yml
xo_host: your-xo-hostname
xo_user: your_username
xo_password: your_password
then reference these variables from the inventory configuration:
# my.xen_orchestra.yml
plugin: community.general.xen_orchestra
api_host: "{{ xo_host }}"
user: "{{ xo_user }}"
password: "{{ xo_password }}"
use_ssl: true
validate_certs: true
When using Ansible Vault, add --ask-vault-pass (or --vault-password-file ~/.vault_pass.txt) and -e @vault.yml to your commands.
Using the inventory
# ansible-inventory -i my.xen_orchestra.yml --list # ansible-inventory -i my.xen_orchestra.yml --graph # ansible-inventory -i my.xen_orchestra.yml --host=uuid-of-your-vm # ansible -i my.xen_orchestra.yml all -m ping
--list dumps the whole inventory, --graph displays the groups as a tree, and --host shows the variables of a single VM (identified by its UUID in XO). For each VM, the plugin exposes its XO properties as host variables:
{
"ansible_host": "192.0.2.10",
"cpus": 1,
"has_ip": true,
"ip": "192.0.2.10",
"is_managed": true,
"memory": 2147467264,
"name_label": "XO Tutorial",
"os_version": {
"distro": "Ubuntu",
"name": "Ubuntu 24.04",
"uname": "6.8.0-57-generic"
},
"power_state": "running",
"tags": [],
"type": "VM",
"uuid": "0ae54d06-e3e2-100c-00e8-46f67945e37c"
}
Groups and host variables
VMs are automatically grouped under their pool and host. On top of that, the plugin supports the standard constructed-inventory options: groups (conditional groups), keyed_groups (one group per property value), and compose (custom host variables), all evaluated against the host variables shown above.
plugin: community.general.xen_orchestra
api_host: your-xo-hostname
user: your_username
password: your_password
use_ssl: true
validate_certs: true
# Conditional groups
groups:
running: power_state == "running"
halted: power_state == "halted"
ubuntu: "'Ubuntu' in name_label"
critical: "'critical' in tags"
# One group per value of a property
keyed_groups:
- prefix: state
key: power_state
# Custom host variables
compose:
ansible_user: "'ubuntu' if 'Ubuntu' in name_label else 'root'"
xo_vm_name: name_label
power_state is lowercase in the inventory (running, halted), even though XO displays Running and Halted in its UI.
Using it in playbooks
Any group built by the inventory can be targeted in a playbook. This one updates every running Debian or Ubuntu VM, using the running group defined above:
# update-running-vms.yml
---
- name: Update all running Debian and Ubuntu VMs
hosts: running
gather_facts: true
become: true
tasks:
- name: Update package index and upgrade packages
ansible.builtin.apt:
update_cache: true
upgrade: dist
when: ansible_distribution in ["Ubuntu", "Debian"]
- name: Check if a reboot is required
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_required
when: ansible_distribution in ["Ubuntu", "Debian"]
- name: Reboot if required
ansible.builtin.reboot:
when: reboot_required.stat.exists | default(false)
# ansible-playbook -i my.xen_orchestra.yml update-running-vms.yml
Troubleshooting
- Connection problems: check
api_host(hostname only, no scheme), the credentials, and thatuse_sslmatches how your XO is exposed (HTTPS or plain HTTP). - Empty inventory: make sure the XO account can actually see the VMs (an unprivileged user only sees the objects it has access to).
- Missing
ansible_host: a VM without guest tools reports no IP address;has_ipisfalseandansible_hoststays empty.
For more detail, run the inventory in debug mode:
# ANSIBLE_DEBUG=1 ansible-inventory -i my.xen_orchestra.yml --list
Going further
The plugin documentation is the reference for every option (caching, use_vm_uuid, use_host_uuid, and more) and is always up to date with the collection, and the plugin source code is available in the community.general repository.