After completing this section, you should be able to control what happens when a task fails, and what conditions cause a task to fail.
Ansible evaluates the return code of each task to determine whether the task succeeded or failed. Normally, when a task fails Ansible immediately aborts the rest of the play on that host, skipping all subsequent tasks.
However, sometimes you might want to have play execution continue even if a task fails. For example, you might expect that a particular task could fail, and you might want to recover by running some other task conditionally. There are a number of Ansible features that can be used to manage task errors.
By default, if a task fails, the play is aborted. However, this behavior can be overridden by ignoring failed tasks. You can use the ignore_errors keyword in a task to accomplish this.
The following snippet shows how to use ignore_errors in a task to continue playbook execution on the host even if the task fails. For example, if the notapkg package does not exist then the yum module fails, but having ignore_errors set to yes allows execution to continue.
- name: Latest version of notapkg is installed
yum:
name: notapkg
state: latest
ignore_errors: yesNormally when a task fails and the play aborts on that host, any handlers that had been notified by earlier tasks in the play will not run. If you set the force_handlers: yes keyword on the play, then notified handlers are called even if the play aborted because a later task failed.
The following snippet shows hows to use the force_handlers keyword in a play to force execution of the handler even if a task fails:
---
- hosts: all
force_handlers: yes
tasks:
- name: a task which always notifies its handler
command: /bin/true
notify: restart the database
- name: a task which fails because the package doesn't exist
yum:
name: notapkg
state: latest
handlers:
- name: restart the database
service:
name: mariadb
state: restartedRemember that handlers are notified when a task reports a changed result but are not notified when it reports an ok or failed result.
You can use the failed_when keyword on a task to specify which conditions indicate that the task has failed. This is often used with command modules that may successfully execute a command, but the command's output indicates a failure.
For example, you can run a script that outputs an error message and use that message to define the failed state for the task. The following snippet shows how the failed_when keyword can be used in a task:
tasks:
- name: Run user creation script
shell: /usr/local/bin/create_users.sh
register: command_result
failed_when: "'Password missing' in command_result.stdout"The fail module can also be used to force a task failure. The above scenario can alternatively be written as two tasks:
tasks:
- name: Run user creation script
shell: /usr/local/bin/create_users.sh
register: command_result
ignore_errors: yes
- name: Report script failure
fail:
msg: "The password is missing in the output"
when: "'Password missing' in command_result.stdout"You can use the fail module to provide a clear failure message for the task. This approach also enables delayed failure, allowing you to run intermediate tasks to complete or roll back other changes.
When a task makes a change to a managed host, it reports the changed state and notifies handlers. When a task does not need to make a change, it reports ok and does not notify handlers.
The changed_when keyword can be used to control when a task reports that it has changed. For example, the shell module in the next example is being used to get a Kerberos credential which will be used by subsequent tasks. It normally would always report changed when it runs. To suppress that change, changed_when: false is set so that it only reports ok or failed.
- name: get Kerberos credentials as "admin"
shell: echo "{{ krb_admin_pass }}" | kinit -f admin
changed_when: falseThe following example uses the shell module to report changed based on the output of the module that is collected by a registered variable:
tasks:
- shell:
cmd: /usr/local/bin/upgrade-database
register: command_result
changed_when: "'Success' in command_result.stdout"
notify:
- restart_database
handlers:
- name: restart_database
service:
name: mariadb
state: restartedIn playbooks, blocks are clauses that logically group tasks, and can be used to control how tasks are executed. For example, a task block can have a when keyword to apply a conditional to multiple tasks:
- name: block example
hosts: all
tasks:
- name: installing and configuring Yum versionlock plugin
block:
- name: package needed by yum
yum:
name: yum-plugin-versionlock
state: present
- name: lock version of tzdata
lineinfile:
dest: /etc/yum/pluginconf.d/versionlock.list
line: tzdata-2016j-1
state: present
when: ansible_distribution == "RedHat"Blocks also allow for error handling in combination with the rescue and always statements. If any task in a block fails, tasks in its rescue block are executed in order to recover. After the tasks in the block clause run, as well as the tasks in the rescue clause if there was a failure, then tasks in the always clause run. To summarize:
block: Defines the main tasks to run.
rescue: Defines the tasks to run if the tasks defined in the block clause fail.
always: Defines the tasks that will always run independently of the success or failure of tasks defined in the block and rescue clauses.
The following example shows how to implement a block in a playbook. Even if tasks defined in the block clause fail, tasks defined in the rescue and always clauses are executed.
tasks:
- name: Upgrade DB
block:
- name: upgrade the database
shell:
cmd: /usr/local/lib/upgrade-database
rescue:
- name: revert the database upgrade
shell:
cmd: /usr/local/lib/revert-database
always:
- name: always restart the database
service:
name: mariadb
state: restartedThe when condition on a block clause also applies to its rescue and always clauses if present.