Everything ansible-playbook prints — the PLAY banners, the TASK
headers, the JSON blobs, the recap — is produced by a callback plugin.
None of it is built into the engine, and all of it is configurable
without touching a playbook.
That last clause is the operational point. Changing what a run reports
is a configuration change, reversible, with no diff in any repository
your change process governs.
The one-stdout rule
Read-only / Safewhat ships— Lists the callback plugins available on a bare ansible-core 2.21 controller.
$ ansible-doc -t callback -l
ansible.builtin.default default Ansible screen output
ansible.builtin.junit write playbook output to a JUnit file
ansible.builtin.minimal minimal Ansible screen output
ansible.builtin.oneline oneline Ansible screen output
ansible.builtin.tree Save host events to files
Five, and they divide into two kinds:
One stdout callback, set by DEFAULT_STDOUT_CALLBACK. Its
documentation states the rule directly: you can only have one at a
time.default, minimal and oneline are stdout callbacks and
choosing one replaces the others.
Any number of notification callbacks, listed in
CALLBACKS_ENABLED. These do not own the screen; they write files,
post to services, collect timings. junit and tree are these.
Ten lines become six, and the saving grows with the size of the
structure. On a --diff run, or anything printing a registered result,
this is the difference between readable output and scrolling.
There is a companion option, pretty_results — ANSIBLE_CALLBACK_FORMAT_PRETTY
— and its documentation carries a warning worth quoting, because it is
the sort of thing that costs an hour:
When set to true and used with the yaml result format, this
option will modify module responses in an attempt to produce a more
human friendly output at the expense of correctness, and should not
be relied upon to aid in writing variable manipulations or
conditionals. For correctness, set this option to false or set
result_format to json.
Machine-readable artefacts
junit — a test report for CI
Read-only / SafeJUnit XML from an ordinary run— Enables the junit callback for one run and writes the report into a directory. No playbook change.
Every CI system on earth renders JUnit XML, so an Ansible run becomes a
test report with per-task results and timings, for free, from an
environment variable.
Note what is inside <system-out>: the full task result.
tree — one file per host
Writes each host’s results as JSON into a directory, which is the
shape you want for per-host audit artefacts.
Read-only / Safeper-host files— Enables the tree callback for one run, writing one JSON file per host.
$ ANSIBLE_CALLBACKS_ENABLED=ansible.builtin.tree ANSIBLE_CALLBACK_TREE_DIR=./tree ansible-playbook -i inv.ini small.yml && ls ./tree
web01.example.com
web02.example.com
profile_tasks — where the time went
ansible.posix.profile_tasks prints a per-task duration and a summary
of the slowest tasks. It is the first thing to reach for when a run
that used to take eleven minutes takes forty.
It lives in the ansible.posix collection, so it is not present on a
bare ansible-core controller — one more instance of Part XXVII’s
theme, that the tool people assume is built in is a pinned dependency.
Part XXXIV covers performance work properly. The relevant discipline
here is the general one: measure before tuning, and a callback is
how you measure without changing the thing you are measuring.
Knowledge check
Knowledge check · 4 questions
Q1. You want per-task timings and a JUnit report from the same run. How many stdout callbacks and how many notification callbacks does that need?
Q2. A task result printed with result_format: yaml shows enabled: true, but a conditional written against it behaves unexpectedly. What is the likely explanation?
Q3. Which are true about enabling the junit callback in CI? Select all that apply.
Q4. ansible.builtin.tree and ansible.builtin.oneline both carry deprecation notices in 2.21 naming ansible-core 2.23 as their removal version.
Passing score: 75%. Answers are checked in this browser.