Readable, per-host summaries of ansible-playbook --check --diff runs, with explicit flagging of tasks that cannot be previewed.
Project description
playcheck
Readable previews for ansible-playbook --check --diff.
Ansible's check mode tells you what a playbook would change — but the raw
output is an unstructured scroll, and tasks that can't be simulated
(shell, command, raw, …) are silently skipped with the same
skipping: line as an ordinary conditional skip. People read "no diff shown"
as "no change", and that's false.
playcheck runs the check for you and reformats the result so it's readable in ten seconds:
- Per-host grouping —
web-01: 4 changes · 2 not previewable - Colored add/remove diffs instead of raw unified-diff walls
- Explicit flagging of every task that could not be previewed, including
tasks with
check_mode: falsethat executed for real during the check - Top-line summary — hosts affected, tasks that would change, tasks that could not be simulated
$ playcheck run site.yml -i inventory.ini
web-01 4 changes · 2 not previewable
~ Write nginx config (copy)
+server {
+ listen 80;
+}
! Restart nginx (shell) NOT PREVIEWED — Command would have run if not in check mode
~ Write API token (copy) [diff censored (no_log)]
SUMMARY
hosts: 2 of 2 would change
tasks: 5 would change (2 with hidden diffs)
⚠ 3 tasks were NOT simulated (module does not support check mode) — the real run may change more than shown.
Install
pipx install playcheck # or: pip install playcheck
Requires Python ≥ 3.9 and an existing ansible-playbook on PATH
(ansible-core ≥ 2.9). No other dependencies.
Usage
playcheck run <playbook> -i <inventory> [-l LIMIT] [-t TAGS] [--no-color] [--quiet]
Anything after -- is passed to ansible-playbook unchanged:
playcheck run site.yml -i prod.ini -- -e env=prod --vault-password-file .vault
playcheck adds --check --diff itself — it never applies changes. The exit
code mirrors ansible-playbook's (0 on success even when changes are
pending; non-zero on task failures or unreachable hosts).
For CI gating there are two opt-in exit codes, checked in this order after a clean ansible run:
| flag | exit code | meaning |
|---|---|---|
--fail-on-changes |
3 | at least one task would change something |
--fail-on-unpreviewable |
4 | at least one task could not be simulated |
--format markdown emits a GitHub-flavored report (collapsible per-host
sections, fenced diffs) suitable for PR comments and $GITHUB_STEP_SUMMARY.
GitHub Action
Post the preview as a PR comment, terraform plan-style. The comment is
updated in place on subsequent pushes instead of spamming the thread:
on: pull_request
jobs:
preview:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: Cjayy77/Playcheck@v0
with:
playbook: site.yml
inventory: inventory/prod.ini
ansible-core-version: "2.17" # optional
extra-args: "-l web -e env=prod"
How it works
playcheck doesn't scrape ansible's human-readable output (which is ambiguous
— an unsupported-check-mode skip and a when:-conditional skip print
identically). Instead it ships a tiny stdout callback plugin and invokes
ansible-playbook with ANSIBLE_STDOUT_CALLBACK=playcheck_jsonl, receiving
one structured JSON event per task result, including diffs, skip reasons, and
check-mode metadata.
Safety notes:
no_logresults are censored by Ansible before they reach any callback; playcheck never sees the secret.diff: falsetasks arrive with an empty diff; playcheck marks them[diff hidden by task setting]rather than pretending nothing changed.- Any skip that can't be positively attributed to a
when:condition is flagged as not previewed. Over-flagging is a feature: silently missing an unsimulated task is the exact failure this tool exists to prevent.
Status
Alpha. CLI formatter and GitHub Action work; GitLab CI wrapper is planned.
Verified against ansible-core 2.15, 2.17, 2.19, and 2.21 with real
--check --diff runs — the test suite replays against fresh captures from
each version (scripts/version_matrix.sh, also run in CI), not hand-written
fixtures. Classification never depends on exact skip-message text: any skip
that can't be positively attributed to a when: conditional is flagged, which
is what catches raw (skipped with no message at all) and whatever future
ansible versions do differently.
Development
pip install -e . pytest
pytest
# regenerate fixtures from a real run (Linux/WSL, needs ansible-core):
ANSIBLE_STDOUT_CALLBACK=playcheck_jsonl \
ANSIBLE_CALLBACK_PLUGINS=src/playcheck/_ansible/callback_plugins \
ansible-playbook testdata/site.yml -i testdata/inventory.ini --check --diff \
> tests/fixtures/jsonl_output.jsonl
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file playcheck-0.1.0.tar.gz.
File metadata
- Download URL: playcheck-0.1.0.tar.gz
- Upload date:
- Size: 19.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
87a4c3303ec52d64831bc253b2abcc7a1f38d01ccf41883c03391d60042e6852
|
|
| MD5 |
5ab7d77a4996875de1b854a60ed8f3e3
|
|
| BLAKE2b-256 |
056977da6c35a68be3e43697b154f5cb1843ad5a158b684ca3f8e28b600eb4e0
|
Provenance
The following attestation bundles were made for playcheck-0.1.0.tar.gz:
Publisher:
release.yml on Cjayy77/Playcheck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
playcheck-0.1.0.tar.gz -
Subject digest:
87a4c3303ec52d64831bc253b2abcc7a1f38d01ccf41883c03391d60042e6852 - Sigstore transparency entry: 2055024657
- Sigstore integration time:
-
Permalink:
Cjayy77/Playcheck@6b32e23cc1b93bc2f4468b6a593522e4eca418a3 -
Branch / Tag:
refs/tags/0.1.0 - Owner: https://github.com/Cjayy77
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6b32e23cc1b93bc2f4468b6a593522e4eca418a3 -
Trigger Event:
release
-
Statement type:
File details
Details for the file playcheck-0.1.0-py3-none-any.whl.
File metadata
- Download URL: playcheck-0.1.0-py3-none-any.whl
- Upload date:
- Size: 17.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b54abcb5eccd5df2acd80e1b8c1ad55557994e2a9bcfb41c87daac2a066d71f2
|
|
| MD5 |
d5361a1d70893e53b5d9c12e4c5e4e4a
|
|
| BLAKE2b-256 |
5826862e33b48983c4ac3e560f5d3684e792d944b513beb1106a9889a216c6a2
|
Provenance
The following attestation bundles were made for playcheck-0.1.0-py3-none-any.whl:
Publisher:
release.yml on Cjayy77/Playcheck
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
playcheck-0.1.0-py3-none-any.whl -
Subject digest:
b54abcb5eccd5df2acd80e1b8c1ad55557994e2a9bcfb41c87daac2a066d71f2 - Sigstore transparency entry: 2055025206
- Sigstore integration time:
-
Permalink:
Cjayy77/Playcheck@6b32e23cc1b93bc2f4468b6a593522e4eca418a3 -
Branch / Tag:
refs/tags/0.1.0 - Owner: https://github.com/Cjayy77
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@6b32e23cc1b93bc2f4468b6a593522e4eca418a3 -
Trigger Event:
release
-
Statement type: