unit 5 · Logic
Blocks & error handling
try/catch/finally you already know
you may want to read 15 · Registering output & debug , 18 · Conditionals with when first — but nothing is locked
tl;dr
- A failed task drops that host from the play — every other host carries on. Containment is per host.
- failed_when redefines failure from the result; ignore_errors just barrels past a real one.
- block groups tasks; rescue runs on failure, always runs regardless — try/catch/finally.
why it matters Production playbooks need rollback paths and checks that may "fail" — without failed_when and rescue you either bury real errors or let one bad exit code strand a host mid-deploy.
Lesson 15 ended with a warning shot: register nginx -t, and if the
config is broken the task fails and the play stops before your debug ever
runs. This lesson fixes it. First, be precise about what “stops” means. When a task fails, Ansible pulls that host out of the
rest of the play — every other host carries on. One broken box in a
twenty-host run kills the run only for that box, and the recap counts
it under failed. That per-host containment is the
default, and most days it’s exactly what you want. Everything below is
for the days it isn’t.
Two dials on a single task
The blunt dial is ignore_errors: true. The task still runs, still
fails, still prints its red error with ...ignoring appended — but the
host stays in the play, and the recap files it under ignored, not
failed. Use it for genuinely optional work, and sparingly: it doesn’t
make a task succeed, it just declares that nobody cares.
The precise dial is failed_when: — it redefines what failure means
for one task. Like when: from lesson 18 it takes a bare Jinja2
expression, no braces. It’s evaluated against the task’s own result, so
it pairs naturally with register . Here’s lesson 15’s
trap, defused:
- name: Check nginx config syntax
ansible.builtin.command: nginx -t
register: cfg_check
failed_when: false # a nonzero rc no longer fails the host
- name: Reload nginx only if the config parses
ansible.builtin.service:
name: nginx
state: reloaded
when: cfg_check.rc == 0 # the registered rc drives the decisionfailed_when: false means the check can never fail the host. The exit
code lands safely in cfg_check, and a later task decides what to do
about it. The other everyday shape is rc-based. Plenty of commands use
nonzero exit codes as answers, not errors — grep returns 1 for “no
match” — so failed_when: grep_result.rc not in [0, 1] fails only on a
genuine error code. Its sibling changed_when: redefines the changed
verdict the same way. That dial is the heart of lesson 22.
block, rescue, always
A block groups tasks. Any keyword you put on the
block — a when:, a become: — applies to every task inside. That is
the “one condition on a whole group of tasks” lesson 18 promised. But
blocks live in this lesson for the two sections you can attach. rescue: runs only if a task in the block fails.
always: runs no matter what happened.
- name: Deploy with a rollback path
block:
- name: Run the database migration
ansible.builtin.command: /opt/app/bin/migrate.sh
rescue:
- name: Roll back to the previous release
ansible.builtin.command: /opt/app/bin/rollback.sh
always:
- name: Re-enable monitoring alerts
ansible.builtin.command: /opt/app/bin/alerts.sh enableBlock tasks run in order. The first failure jumps straight to rescue:,
skipping the rest of the block on that host. If the rescue tasks succeed,
the host is healthy again — it continues with the rest of the play, and
the recap shows rescued=1 instead of a failure. Inside a
rescue, two variables tell you what broke: ansible_failed_task and
ansible_failed_result hold the failing task and its result dict, ready
for lesson 15’s debug treatment.
One habit ties this together: reach for failed_when: before
ignore_errors:, and for rescue: when the response to failure is real
work — rollback, cleanup, re-arming alerts — rather than a shrug. Your recap vocabulary is now complete: ok, changed and skipping
from earlier lessons (the recap column spells it skipped), joined by
failed, unreachable, rescued and ignored. Next lesson takes the one verdict that still lies — command
reporting changed for work it never did — and makes it tell the truth.
self-check — recall, not a test
1 One host of twenty fails a task mid-play, no error handling anywhere. What happens to that host, and to the other nineteen?
2 ignore_errors and failed_when both keep a run alive — when is each the right tool?
3 Map block/rescue/always to PowerShell, and name one failure rescue will not catch.
lab (optional — never required)
Against localhost: put a failing command in a block, debug ansible_failed_result in rescue, add an always task — then watch the recap report rescued=1 and the play finish clean.
needs: The control node from lesson 5.