Chapter 5: Implementing Task Control
Exercise: Handling task failure
You will make a task fail on purpose, then handle that failure four different ways: ignore it, rescue it with a block, stop a read-only command from reporting changes, and invent your own failure condition.
The inventory in ~/control-errors puts servera.lab.example.com in the databases group.
Handling task failure
- Ignore failed commands during playbook execution.
- Override what counts as failed or changed for a task.
- Implement block, rescue and always.
Prerequisites: Complete control-node setup and the preceding lessons in this chapter. Run commands as student on workstation unless a step names another machine.
From the project directory, run the relevant home-lab check. For an exercise with several checkpoints, grade each state before changing or removing it:
lab grade control-errorsThe grader reads project files and host state. A passing report covers the listed checks; also perform the task’s independent connection, repeat-run, and reboot checks where requested.
Remove the artificial failure condition and compare the recap with the previous recovered run.
Record the published result first. The grader checks the original requirements, so a changed name or value may intentionally fail those checks.
Explore failure reporting in playbook.yml without confusing it with the underlying host state.
- Use the intentionally nonexistent web package
httpto demonstrate an ordinary failure, then an ignored failure. - Demonstrate recovery that installs MariaDB after the web task fails and a final action that starts MariaDB for both successful and recovered paths.
- Correct the package to
httpdand prove that the recovery path is skipped. - A read-only time check must capture and display its result without reporting a change. Finally, make the successful web task report failure deliberately and explain why recovery runs.
- Keep evidence of each stage before advancing to the next checkpoint.
Write a play with a deliberate mistake
In ~/control-errors, create playbook.yml. The value http is intentionally wrong: there is no package with that name.
---
- name: Task Failure Exercise
hosts: databases
vars:
web_package: http
db_package: mariadb-server
db_service: mariadb
tasks:
- name: Install {{ web_package }} package
ansible.builtin.dnf:
name: "{{ web_package }}"
state: present
- name: Install {{ db_package }} package
ansible.builtin.dnf:
name: "{{ db_package }}"
state: presentRun it and watch the play stop
[student@workstation control-errors]$ ansible-navigator run -m stdout playbook.yml
...output omitted...
TASK [Install http package] ****************************************************
fatal: [servera.lab.example.com]: FAILED! => {"changed": false, "failures":
["No package http available."], "msg": "Failed to install some of the specified
packages", "rc": 1, "results": []}
PLAY RECAP *********************************************************************
servera.lab.example.com : ok=1 changed=0 unreachable=0 failed=1 ...The second task never ran.
Ignore the error
Add ignore_errors: true to the first task and run again. The failure is reported as ...ignoring, the second task runs, and the recap shows ignored=1.
- name: Install {{ web_package }} package
ansible.builtin.dnf:
name: "{{ web_package }}"
state: present
ignore_errors: trueRescue the failure with a block
Remove ignore_errors. Put the web package task in a block, the database package task in rescue, and add an always section that starts the database service.
tasks:
- name: Attempt to set up a webserver
block:
- name: Install {{ web_package }} package
ansible.builtin.dnf:
name: "{{ web_package }}"
state: present
rescue:
- name: Install {{ db_package }} package
ansible.builtin.dnf:
name: "{{ db_package }}"
state: present
always:
- name: Start {{ db_service }} service
ansible.builtin.service:
name: "{{ db_service }}"
state: startedRun it: block fails, rescue and always run
[student@workstation control-errors]$ ansible-navigator run -m stdout playbook.yml
...output omitted...
TASK [Install http package] ****************************************************
fatal: [servera.lab.example.com]: FAILED! => {"changed": false, ...}
TASK [Install mariadb-server package] ******************************************
ok: [servera.lab.example.com]
TASK [Start mariadb service] ***************************************************
changed: [servera.lab.example.com]
PLAY RECAP *********************************************************************
servera.lab.example.com : ok=3 changed=1 unreachable=0 failed=0
skipped=0 rescued=1 ignored=0Note failed=0 and rescued=1: the rescue handled the failure.
Fix the package name
Change web_package to httpd and run again. The block succeeds, so rescue is skipped, and always still runs.
Add a read-only command
Before the block, add a task that runs date and registers the output, and a task that prints it.
- name: Check local time
ansible.builtin.command: date
register: command_result
- name: Print local time
ansible.builtin.debug:
var: command_result.stdoutRun the playbook twice: Check local time reports changed every time, although date changes nothing on the host. The command module cannot tell the difference between reading and changing.
Tell Ansible the command changes nothing
Add changed_when: false to Check local time and run again. It now reports ok, and the recap shows changed=0, while the time is still captured and printed.
Invent a failure condition
Add failed_when: web_package == "httpd" to the task inside the block, and run the playbook.
block:
- name: Install {{ web_package }} package
ansible.builtin.dnf:
name: "{{ web_package }}"
state: present
failed_when: web_package == "httpd"TASK [Install httpd package] ***************************************************
fatal: [servera.lab.example.com]: FAILED! => {"changed": false,
"failed_when_result": true, "msg": "Nothing to do", "rc": 0, "results": []}
TASK [Install mariadb-server package] ******************************************
ok: [servera.lab.example.com]
...output omitted...
servera.lab.example.com : ok=5 changed=0 unreachable=0 failed=0
skipped=0 rescued=1 ignored=0dnf actually succeeded ("Nothing to do", rc: 0), but failed_when changed how it was reported, and that report was enough to send the play into rescue.
Finish
Check your work with lab grade control-errors, then put the exercise away with lab finish control-errors.