When migrating an Ansible inventory (e.g., from legacy static inventory to dynamic sources like Foreman, NetBox, or custom CMDB), validation is critical to ensure no configuration drift is introduced.
This method provides a deterministic, host-level comparison by:
This approach avoids ambiguity from raw inventory diffs and focuses on effective runtime data.
The verification is based on three core principles:
hostvars)This ensures:
Use an Ansible playbook to export the effective inventory state per host.
inventory_dump.yml- name: Export inventory state per host
hosts: all
gather_facts: false
vars:
outdir: "/tmp/inventory_snapshot"
tasks:
- name: Ensure output directory exists
ansible.builtin.file:
path: "{{ outdir }}"
state: directory
mode: "0755"
run_once: true
delegate_to: localhost
- name: Read effective host vars exactly like ansible-inventory --host
ansible.builtin.command: >-
ansible-inventory
{% for src in ansible_inventory_sources %}
-i {{ src | quote }}
{% endfor %}
--host {{ inventory_hostname | quote }}
register: inventory_host_json
changed_when: false
delegate_to: localhost
- name: Write snapshot file per host
ansible.builtin.copy:
dest: "{{ outdir }}/{{ inventory_hostname }}.txt"
mode: "0644"
content: |-
vars:
{{ (inventory_host_json.stdout | from_json | to_nice_yaml(indent=2)) | indent(2, true) }}
groups:
{% for g in group_names | sort %}
- {{ g }}
{% endfor %}
delegate_to: localhost
Run this playbook against both inventories:
# Old inventory
ansible-playbook -i inventory_old inventory_dump.yml
mv /tmp/inventory_snapshot /tmp/inventory_old
# New inventory
ansible-playbook -i inventory_new inventory_dump.yml
mv /tmp/inventory_snapshot /tmp/inventory_new
Use a checksum-based comparison script.
compare.inventory.sh#!/usr/bin/env bash
set -euo pipefail
DIR1="${1:-/tmp/inventory_old}"
DIR2="${2:-/tmp/inventory_new}"
if [[ ! -d "$DIR1" || ! -d "$DIR2" ]]; then
echo "ERROR: both arguments must be existing directories"
echo "Usage: $0 <left_dir> <right_dir>"
exit 2
fi
tmp_all=$(mktemp)
trap 'rm -f "$tmp_all"' EXIT
{
find "$DIR1" -maxdepth 1 -type f -printf '%f\n'
find "$DIR2" -maxdepth 1 -type f -printf '%f\n'
} | sort -u > "$tmp_all"
rc=0
while IFS= read -r file; do
left="$DIR1/$file"
right="$DIR2/$file"
if [[ ! -f "$left" ]]; then
printf 'ONLY_RIGHT %s\n' "$file"
rc=1
continue
fi
if [[ ! -f "$right" ]]; then
printf 'ONLY_LEFT %s\n' "$file"
rc=1
continue
fi
md5_left=$(md5sum "$left" | awk '{print $1}')
md5_right=$(md5sum "$right" | awk '{print $1}')
if [[ "$md5_left" == "$md5_right" ]]; then
printf 'SAME %s\n' "$file"
else
printf 'DIFF %s\n' "$file"
printf ' LEFT : %s\n' "$md5_left"
printf ' RIGHT: %s\n' "$md5_right"
rc=1
fi
done < "$tmp_all"
exit "$rc"
bash compare.inventory.sh /tmp/inventory_old /tmp/inventory_new
| Result | Meaning |
|---|---|
SAME |
Host configuration is identical |
DIFF |
Host exists in both but differs |
ONLY_LEFT |
Host missing in new inventory |
ONLY_RIGHT |
Host missing in old inventory |
SAME deploy0.domain.tld.txt
DIFF confl.domain.tld.txt
LEFT : 2055155554a6f7a93730f4be8a1ea859
RIGHT: 776954e90d567b35c971c03987d63807
Interpretation:
deploy0 → fully consistentconfl → configuration drift detected → requires inspectionFor hosts marked as DIFF, run:
diff -u /tmp/inventory_old/host.txt /tmp/inventory_new/host.txt
This reveals:
Raw inventory comparison is unreliable due to:
This method compares effective state, not source definitions.
The playbook excludes:
ansible_* → runtime/internal factsinventory_* → internal metadatagroup_names, groups → handled separatelyThis ensures only relevant configuration data is compared.
Checksums provide:
Detailed diffs are only needed for exceptions.
DIFF as a migration blockermd5sum with sha256sum for stricter hashingThis method provides a reproducible, scalable, and precise way to validate Ansible inventory migrations.
It ensures that:
For large environments, this approach scales cleanly and integrates well into automation workflows.