Metadata-Version: 2.3
Name: inventory-guard
Version: 0.2.0
Summary: Semantic change guard for Ansible inventories - detect unexpected infrastructure changes
Keywords: ansible,inventory,guard,ci-cd,testing,devops
Author: Maarten
Author-email: Maarten <ikmaarten@gmail.com>
License: MIT License
         
         Copyright (c) 2025 Maarten
         
         Permission is hereby granted, free of charge, to any person obtaining a copy
         of this software and associated documentation files (the "Software"), to deal
         in the Software without restriction, including without limitation the rights
         to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         copies of the Software, and to permit persons to whom the Software is
         furnished to do so, subject to the following conditions:
         
         The above copyright notice and this permission notice shall be included in all
         copies or substantial portions of the Software.
         
         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         SOFTWARE.
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: System Administrators
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Systems Administration
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: Utilities
Requires-Dist: ruamel-yaml>=0.18,<0.19
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/maartenq/inventory-guard
Project-URL: Issues, https://github.com/maartenq/inventory-guard/issues
Project-URL: Repository, https://github.com/maartenq/inventory-guard
Description-Content-Type: text/markdown

# 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):

```sh
uv add inventory-guard
```

With `pip`:

```sh
pip install inventory-guard
```

## Quick Start

Compare two inventory files (silent on success):

```sh
# 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 2.0

# Using short flags (concise)
inventory-guard -c inventory/prod.yml -n inventory/prod-updated.yml
```

Get verbose output to see what's happening:

```sh
inventory-guard -v -c inventory/prod.yml -n inventory/prod-updated.yml
```

Get JSON summary for further processing:

```sh
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`:

```toml
[inventory_guard]
current = "inventory/prod.yml"
new = "inventory/prod-updated.yml"

max_host_change_pct = 5.0
max_var_change_pct = 2.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:

```sh
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: 2.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

1. **Loads both inventories**: Parses YAML with Ansible vault tag support
2. **Computes effective variables**: Merges group vars and host vars following
   Ansible precedence
3. **Compares hosts**: Detects added/removed hosts
4. **Compares variables**: For common hosts, counts variable key additions,
   removals, and value changes
5. **Applies thresholds**: Fails if changes exceed configured limits
6. **Generates reports**: Outputs JSON summary and optional Markdown report

## Exit Codes

- `0`: Success - changes are within acceptable thresholds
- `1`: 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 `-v` for INFO logs, `-vv` for
  DEBUG logs
- **Errors (exit 1, 2)**: Error messages logged to stderr as JSON
- **JSON output**: Only to stdout when `--json` flag is used
- **Reports**: Written to files when `--json-out` or `--report` specified

### Logging Format

Logs are structured JSON on stderr for easy parsing:

```json
{"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:**
```yaml
# .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:**
```yaml
# .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

```yaml
# .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

```sh
# 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

1. **CLI arguments** (highest priority)
2. **Config file** values
3. **Built-in defaults** (lowest priority)

## Advanced Features

### Ansible Vault Support

Inventory Guard parses `!vault` tags as opaque strings. Encrypted values are
compared as-is:

```yaml
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:

```sh
--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:

```sh
--ignore-key-regex '^build_id$' \
--ignore-key-regex '^generated_at$'
```

## Development

Clone and setup:

```sh
git clone https://github.com/maartenq/inventory-guard.git
cd inventory_guard
task install
```

Run tests and checks:

```sh
# 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:

```sh
# Update version in pyproject.toml, commit, then:
git tag 0.2.0
git push origin 0.2.0
# 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:
1. Fork the repository
2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/inventory-guard.git`
3. Install dependencies: `uv sync`
4. Run tests: `uv run pytest`
5. Run checks: `uv run mypy src/ && pre-commit run --all-files`
6. Submit a pull request
