Inventory Guard
A semantic change guard for Ansible inventories that detects unexpected infrastructure changes before they reach production.
What It Does
Inventory Guard compares two Ansible inventory files (current vs. new) and flags changes that exceed your configured thresholds. Instead of blindly accepting inventory updates, you can:
- Catch accidents: Detect when a typo removes 50 hosts instead of 5
- Prevent drift: Alert when variable changes exceed expected patterns
- Gate CI/CD: Block merges or deployments when changes look suspicious
- Audit changes: Generate reports showing exactly what changed
Installation
With uv (recommended):
uv add inventory-guard
With pip:
pip install inventory-guard
Quick Start
Compare two inventory files (silent on success):
# Using long flags (explicit)
inventory-guard \
--current inventory/prod.yml \
--new inventory/prod-updated.yml \
--max-host-change-pct 5.0 \
--max-var-change-pct 5.0
# Using short flags (concise)
inventory-guard -c inventory/prod.yml -n inventory/prod-updated.yml
Get verbose output to see what's happening:
inventory-guard -v -c inventory/prod.yml -n inventory/prod-updated.yml
Get JSON summary for further processing:
inventory-guard --json -c inventory/prod.yml -n inventory/prod-updated.yml | jq
By default, successful runs produce no output (Unix philosophy: no news is good
news). Use -v for INFO logs or --json for machine-readable output.
Configuration File
Create inventory_semantic_guard.toml:
[inventory_guard]
current = "inventory/prod.yml"
new = "inventory/prod-updated.yml"
max_host_change_pct = 5.0
max_var_change_pct = 5.0
max_host_change_abs = 10
max_var_change_abs = 50
# Ignore volatile keys that change frequently
ignore_key_regex = [
"^build_id$",
"^last_updated$",
"^timestamp$"
]
# Treat these as unordered sets (order doesn't matter)
set_like_key_regex = [
"^foreman_host_collections$"
]
# Optional outputs
json_out = "changes.json"
report = "changes.md"
Then run without arguments:
inventory-guard
CLI Options
--config PATH Path to TOML config (default:
./inventory_semantic_guard.toml)
-c, --current PATH Current inventory file (required unless in config)
-n, --new PATH New inventory file (required unless in config)
--max-host-change-pct N Max % of hosts that can be added/removed
(default: 5.0)
--max-var-change-pct N Max % of variable keys that can change
(default: 5.0)
--max-host-change-abs N Absolute cap on host changes (default: 0 = disabled)
--max-var-change-abs N Absolute cap on variable changes
(default: 0 = disabled)
--ignore-key-regex REGEX Variable keys to ignore (repeatable)
--set-like-key-regex REGEX Treat list values as unordered sets (repeatable)
-v, --verbose Increase verbosity (-v for INFO, -vv for DEBUG)
--json Output JSON summary to stdout
--json-out PATH Write JSON summary to file
--report PATH Write Markdown report to file
How It Works
- Loads both inventories: Parses YAML with Ansible vault tag support
- Computes effective variables: Merges group vars and host vars following Ansible precedence
- Compares hosts: Detects added/removed hosts
- Compares variables: For common hosts, counts variable key additions, removals, and value changes
- Applies thresholds: Fails if changes exceed configured limits
- Generates reports: Outputs JSON summary and optional Markdown report
Exit Codes
0: Success - changes are within acceptable thresholds1: Error - file not found, invalid YAML, bad configuration, etc.2: Guard failure - changes exceed configured thresholds
Output Behavior
Inventory Guard follows Unix conventions for output:
- Success (exit 0): Silent by default. Use
-vfor INFO logs,-vvfor DEBUG logs - Errors (exit 1, 2): Error messages logged to stderr as JSON
- JSON output: Only to stdout when
--jsonflag is used - Reports: Written to files when
--json-outor--reportspecified
Logging Format
Logs are structured JSON on stderr for easy parsing:
{"timestamp": "2025-11-09T21:46:08", "level": "INFO", "message": "Starting inventory comparison"}
{"timestamp": "2025-11-09T21:46:08", "level": "ERROR", "message": "File not found: inventory.yml"}
Use Cases
CI/CD Pipeline
GitHub Actions:
# .github/workflows/inventory-check.yml
name: Inventory Check
on: [pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install inventory-guard
- run: inventory-guard -c inventory/prod.yml -n inventory/prod-new.yml
GitLab CI:
# .gitlab-ci.yml
inventory-check:
script:
- pip install inventory-guard
- inventory-guard -c inventory/prod.yml -n inventory/prod-new.yml
only:
- merge_requests
Pre-commit Hook
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: inventory-guard
name: Check inventory changes
entry: inventory-guard
language: system
pass_filenames: false
Manual Review
# Generate a detailed report for manual review
inventory-guard \
--current prod.yml \
--new prod-updated.yml \
--report changes.md \
--json-out changes.json
# Review the report
less changes.md
Configuration Precedence
- CLI arguments (highest priority)
- Config file values
- Built-in defaults (lowest priority)
Advanced Features
Ansible Vault Support
Inventory Guard parses !vault tags as opaque strings. Encrypted values are
compared as-is:
all:
hosts:
app-1:
db_password: !vault |
$ANSIBLE_VAULT;1.2;AES256;dev
66643866353263333266393931336439623433646634303233663831316665663234...
Set-like Keys
For variables like host collections where order doesn't matter:
--set-like-key-regex '^foreman_host_collections$'
This treats [A, B, C] and [C, A, B] as identical.
Ignoring Volatile Keys
Some keys change on every run (timestamps, build IDs). Ignore them:
--ignore-key-regex '^build_id$' \
--ignore-key-regex '^generated_at$'
Development
Clone and setup:
git clone https://github.com/maartenq/inventory-guard.git
cd inventory_guard
task install
Run tests and checks:
# Run tests
task test
# Run type checking
task type
# Run linting
task lint
# Run all checks (lint + type)
task check
CI/CD
This project uses GitHub Actions for continuous integration and deployment:
- Quality Assurance: Runs on every push and PR
- Linting (pre-commit hooks)
- Type checking (ty + mypy)
- Tests with coverage reporting
- Release: Triggered by version tags (e.g.,
0.2.0,1.0.0a1)- Validates version is newer than PyPI
- Runs full test suite
- Builds distribution packages
- Publishes to PyPI automatically
- Creates GitHub release with notes
To release a new version:
# Update version in pyproject.toml and src/inventory_guard/__init__.py,
# commit, then:
uv sync
git add .
git commit
git push
git tag 0.4.0
git push --tags
# GitHub Actions will handle the rest
License
MIT License (see LICENSE file)
Contributing
Issues and pull requests welcome at https://github.com/maartenq/inventory-guard
Development setup:
- Fork the repository
- Clone your fork:
git clone https://github.com/YOUR_USERNAME/inventory-guard.git - Install dependencies:
uv sync - Run tests:
uv run pytest - Run checks:
uv run mypy src/ && pre-commit run --all-files - Submit a pull request
Metadata
Release files for inventory-guard 0.3.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| inventory_guard-0.3.2.tar.gz | 12.2 kB | With provenance | Details for inventory_guard-0.3.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| inventory_guard-0.3.2-py3-none-any.whl | Python 3 | none | any | Details for inventory_guard-0.3.2-py3-none-any.whl |
Total release size: 26.9 kB
Release files / inventory_guard-0.3.2.tar.gz
| Download URL | inventory_guard-0.3.2.tar.gz |
|---|---|
| Size | 12.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a6d09e900b651d8b43de12413b761cc6a06bd318f127a741ad4dadc66ed58125
|
|
BLAKE2b-256 checksum How to use checksums |
1bff17cfc5ee8e69b77dc7d9749da908676aed9b36f120d148d4e8c2a6cda4c7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 28, 2026.
Transparency logRelease files / inventory_guard-0.3.2-py3-none-any.whl
| Download URL | inventory_guard-0.3.2-py3-none-any.whl |
|---|---|
| Size | 14.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
243c55259a39ab3607e69fd3f22206f7912f51be443e4fe8eb89ca4edae7e986
|
|
BLAKE2b-256 checksum How to use checksums |
6e1cab83ff7dbb37d617b29f5ae387b0dda6de21777ba1a67cb21a808aee9777
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 28, 2026.
Transparency logSecurity for inventory-guard 0.3.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for inventory_guard-0.3.2.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 28, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.