unit 4 · Playbooks
Registering output & debug
The only state that survives
you may want to read 12 · Playbook anatomy first — but nothing is locked
tl;dr
- register: captures a task's full result dict into a named variable — the only state between tasks.
- Inspect .stdout, .rc and .changed; print them with ansible.builtin.debug (msg or var).
- register + debug is the everyday inspection loop: run, print, read, adjust.
why it matters Playbook output shows only ok or changed — when a run misbehaves, register + debug is how you see what a task actually returned instead of guessing.
Lesson 12’s run output was all status and no substance: ok, changed, a
recap. If a task ran uptime, what it printed appeared nowhere. Lesson 2
told you why — each task copies a module to the node, executes it, deletes
it. State does not persist between tasks. The one survivor is this lesson’s
subject: register .
register: is a task keyword. Give it a name, and Ansible stores the
task’s entire result (a dictionary of everything the module reported back)
in a variable with that name, available to every later task. Capture,
then print:
- name: Read uptime # runs on the managed node
ansible.builtin.command: uptime
register: uptime_result # the result dict survives under this name
- name: Print it # touches no host — just writes to run output
ansible.builtin.debug:
msg: 'Uptime: {{ uptime_result.stdout }}'The first task executes remotely; its result travels back as
uptime_result. The second task, ansible.builtin.debug, changes nothing —
it prints into the run output and reports ok. You met its
msg form in lesson 2: a templated string, so lesson 13’s quoting rules
apply. Its other form is for when you don’t know what a module returns:
- name: Dump everything the task returned
ansible.builtin.debug:
var: uptime_result # a bare variable NAME — no {{ }}, no quotesThat prints every field in the dict. Different modules return different
fields, so this dump is how you discover what a result actually contains. For command and shell, three fields do most of the daily
work:
.stdout— what the command printed (.stderris its sibling)..rc— the exit code.0means success, the same convention as PowerShell’s$LASTEXITCODE..changed— whether Ansible reports the task changed the host.
- name: Check nginx config syntax
ansible.builtin.command: nginx -t
register: cfg_check
- name: Read the verdict
ansible.builtin.debug:
msg: 'rc={{ cfg_check.rc }}, changed={{ cfg_check.changed }}'Two caveats. First, cfg_check.changed is true on
every run: command cannot know whether anything on the host actually
changed, so it always claims a change — fixing that is lesson 22. Second,
if nginx -t exits nonzero, the task fails and the play stops for that
host before your debug ever runs. Controlling when a result counts as
failure is lesson 21.
One scoping note: results are per host. Run that play against twenty
machines and each host carries its own uptime_result, holding what its
command printed. Debug then prints twenty lines, one per host — exactly
what you want when hunting the one box that differs.
Printing is half of what registered results are for. The other half is
decisions: a later task can run only when cfg_check.rc == 0, or skip
work a previous task proved unnecessary. That’s when:, lesson 18 — and
registered variables feed it. For now, build the reflex: when a
run surprises you, register the suspect task’s result and debug it. It
is the cheapest look inside Ansible you will get, and the first move
colleagues make before reaching for -vvv.
self-check — recall, not a test
1 Your command task ran fine, but its output appears nowhere in the run. Why, and what's the fix?
2 You registered a command result as cfg_check. Name the three fields you'd read first and what each tells you.
3 Why does register need to exist at all — what architectural fact makes it necessary?
lab (optional — never required)
Write a two-task playbook against localhost: run uptime via ansible.builtin.command, register the result, print result.stdout with debug — then swap msg for var and read the full dict.
needs: The control node from lesson 5.