Ansible evaluates the return code of each task to determine whether the task succeeded or failed. Normally, when a task fails Ansible immediately skips 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 conditionally running some other task. A number of Ansible features 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 ansible.builtin.dnf module fails, but having ignore_errors set to yes allows execution to continue.
- name: Latest version of notapkg is installed
ansible.builtin.dnf:
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 do 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.
If you have ignore_errors: yes set on a task or for the task's play, if that task fails the failure is ignored.
In that case, the play keeps running and handlers still run, even if you have force_handlers: no set, unless some other error causes the play to fail.
The following snippet shows how to use the force_handlers keyword in a play to force execution of the notified handler even if a subsequent task fails:
---
- hosts: all
force_handlers: yes
tasks:
- name: a task which always notifies its handler
ansible.builtin.command: /bin/true
notify: restart the database
- name: a task which fails because the package doesn't exist
ansible.builtin.dnf:
name: notapkg
state: latest
handlers:
- name: restart the database
ansible.builtin.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.
If you set force_handlers: yes on the play, then any handlers that have been notified are run even if a later task failure causes the play to fail.
Otherwise, handlers are not run at all when a play fails.
Setting force_handlers: yes on a play does not cause handlers to be notified for tasks that report ok or failed; it only causes the handlers to run that have already been notified before the point at which the play failed.
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 might successfully execute a command, but where the command's output indicates a failure.
For example, you can run a script that outputs an error message and then use that message to define the failed state for the task.
The following example shows one way that you can use the failed_when keyword in a task:
tasks:
- name: Run user creation script
ansible.builtin.shell: /usr/local/bin/create_users.sh
register: command_result
failed_when: "'Password missing' in command_result.stdout"The ansible.builtin.fail module can also be used to force a task failure.
You could instead write that example as two tasks:
tasks:
- name: Run user creation script
ansible.builtin.shell: /usr/local/bin/create_users.sh
register: command_result
ignore_errors: yes
- name: Report script failure
ansible.builtin.fail:
msg: "The password is missing in the output"
when: "'Password missing' in command_result.stdout"You can use the ansible.builtin.fail module to provide a clear failure message for the task.
This approach also enables delayed failure, which means that you can 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.
Use the changed_when keyword to control how a task reports that it has changed something on the managed host.
For example, the ansible.builtin.command module in the next example validates the httpd configuration on a managed host.
This task validates the configuration syntax, but nothing is actually changed on the managed host.
Subsequent tasks can use the value of the httpd_config_status variable.
It normally would always report changed when it runs.
To suppress that change report, changed_when: false is set so that it only reports ok or failed.
- name: Validate httpd configuration
ansible.builtin.command: httpd -t
changed_when: false
register: httpd_config_statusThe following example uses the ansible.builtin.shell module and only reports changed if the string "Success" is found in the output of the registered variable.
If it does report changed, then it notifies the handler.
tasks:
- ansible.builtin.shell:
cmd: /usr/local/bin/upgrade-database
register: command_result
changed_when: "'Success' in command_result.stdout"
notify:
- restart_database
handlers:
- name: restart_database
ansible.builtin.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 DNF versionlock plugin
block:
- name: package needed by dnf
ansible.builtin.dnf:
name: python3-dnf-plugin-versionlock
state: present
- name: lock version of tzdata
ansible.builtin.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, then rescue tasks are executed 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 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.
tasks:
- name: Upgrade DB
block:
- name: upgrade the database
ansible.builtin.shell:
cmd: /usr/local/lib/upgrade-database
rescue:
- name: revert the database upgrade
ansible.builtin.shell:
cmd: /usr/local/lib/revert-database
always:
- name: always restart the database
ansible.builtin.service:
name: mariadb
state: restartedThe when condition on a block clause also applies to its rescue and always clauses if present.