Ansible Result Tests
Every test so far has checked a piece of data — a variable’s type, its value. This chapter checks something different: what actually happened when a previous task ran. Paired with register, this is arguably the single most useful pattern in this entire section.
register Recap
From the variables section: attach register: name to a task, and its entire result — including whether it changed anything, succeeded, or failed — becomes available under that name for the rest of the play.
is changed
copy, recall from the idempotency discussion back in the playbook chapter, only reports changed when it genuinely wrote something different:
- name: Result Tests Demo
hosts: servers
tasks:
- name: Copy config file
ansible.builtin.copy:
src: app.conf
dest: /etc/app.conf
register: copy_result
- name: Report if it changed
ansible.builtin.debug:
msg: "The config file was updated"
when: copy_result is changedRun this once, and the report prints — the file didn’t exist there before. Run it again, unchanged, and the report is silently skipped, because copy_result is changed is now false. This is the pattern real playbooks use constantly: “only announce something (or act on it) if it actually happened,” rather than unconditionally after every single run.
Note
This only means something for a genuinely idempotent module like copy. Recall from the idempotency chapter that command/shell report changed on every run, regardless of whether anything really changed — checking is changed against a command task’s result is essentially meaningless, since it’s always true.
is succeeded And is failed
Checking whether a task succeeded or failed requires one piece of setup first: by default, a failed task stops the entire play for that host immediately, which means a later task checking is failed would never even run. ignore_errors: true is what lets execution continue past a failure so a later task can actually inspect it:
- name: Try something that might fail
ansible.builtin.command: /bin/false
register: attempt_result
ignore_errors: true
- name: Report the failure
ansible.builtin.debug:
msg: "The command failed, but we're continuing anyway"
when: attempt_result is failedFAILED - RETRYING
ok: [ubuntu] => {
"msg": "The command failed, but we're continuing anyway"
}Without ignore_errors: true on the first task, this playbook would simply stop at that failure — the second task, and its is failed check, would never be reached at all.
is skipped
Checks whether a previous task was skipped by its own when: condition — useful when a later task’s behavior needs to depend on whether an earlier conditional one actually ran:
- name: Maybe run something
ansible.builtin.debug:
msg: "Conditional task"
when: some_condition
register: maybe_result
- name: React to it being skipped
ansible.builtin.debug:
msg: "The previous task was skipped"
when: maybe_result is skippedBest Practices
- Only rely on
is changedfor genuinely idempotent modules —copy, and others like it — never forcommand/shell, whosechangedstatus doesn’t reflect anything real. - Pair
ignore_errors: truewith any task you plan to checkis failed/is succeededagainst — without it, the check will never be reached, since the play stops at the failure first. - Use
is skippedwhen later logic genuinely depends on whether an earlier conditional task ran — it’s a real, distinct outcome from both success and failure, worth checking explicitly rather than assuming.