You are currently viewing How To Work With Block Rescue & Always Directives In Ansible With Examples

How To Work With Block Rescue & Always Directives In Ansible With Examples

This article will explain how to group multiple tasks using the ansible built-in block directive feature. We will also see how to recover from failures using rescue and always directives.

The block rescue and always directive is similar to python’s try, except and finally block. It allows you to group multiple tasks and easily recover from failures. 

All the examples in the upcoming section are demonstrated using the following lab setup. If you wish to follow along, you can replicate the setup.

RELATED ARTICLE - Four Node Ansible Lab Setup Using Docker

Group Multiple Tasks Using Ansible Block Directive

In ansible, there are several ways to group multiple tasks that achieve an outcome. Consider setting up docker and docker-compose packages as an example. Let’s break down the steps into tasks.

TAKS 1 – Setup Docker Repository

TASK 2 – Update the package index and install docker and docker-compose packages

In total there are two tasks required to install the docker and docker-compose packages. Now there are a couple of ways to group these tasks.

  1. Placing the task one after another will make ansible to run the tasks in the given order. This is the default behavior.
  2. Tagging both tasks with the same tag name. For example “tags: docker-setup”
  3. Grouping both tasks in a separate file and importing them into your playbook.
  4. Group both tasks using the block directive.

Here we will be focusing on the block directive. Below is the playbook to install docker and docker-compose packages in the ubuntu distribution. Both tasks are grouped under the block directive.

---
- hosts: ansubuntu
  gather_facts: True
  become: True

  tasks:
    - name: Docker setup
      block:

        - name: Add Docker Repository
          ansible.builtin.apt_repository:
            repo: "deb https://download.docker.com/linux/ubuntu jammy stable"
            filename: docker
            state: present

        - name: Install Docker & Docker Compose
          ansible.builtin.apt:
            pkg:
              - docker-ce
              - docker-ce-cli
              - containerd.io
              - docker-compose-plugin
            state: present
            update_cache: True        

The play got executed without any errors and below is the output.

Inherit Ansible Directives At Block Level

We have seen how to group a couple of tasks under the block directive and get the job done but there is more to it. You can apply other ansible directives like the conditional statement, error handling, privilege escalations, etc at the block level. 

I am going to run the same playbook but with two modifications. 

  1. I have applied the conditional check where the tasks will only be executed on ubuntu based machines. 
  2. Moved the become directive from the play level to the block level. 
---
- hosts: "*"
  gather_facts: True
  
  tasks:
    - name: Docker setup
      block:

        - name: Add Docker Repository
          ansible.builtin.apt_repository:
            repo: "deb https://download.docker.com/linux/ubuntu jammy stable"
            filename: docker
            state: present

        - name: Install Docker & Docker Compose
          ansible.builtin.apt:
            pkg:
              - docker-ce
              - docker-ce-cli
              - containerd.io
              - docker-compose-plugin
            state: present
            update_cache: True      
      when: ansible_facts['distribution'] | lower == "ubuntu"
      become: True

Take a look at the below output where the facts are collected for all three hosts in my lab setup and both tasks are executed only on ubuntu distribution.

Both the when and become directive at the block level will be inherited by all three tasks separately. This is similar to applying the directives individually at the task level.

Recover Failures At The Block Level Using Rescue Directive

When a task gets failed, no subsequent tasks will be executed on the particular host. There are a couple of strategies available to recover from failure. One such strategy is to use the rescue directive which helps to recover from block-level failures.

RELATED ARTICLE - How To Handle Failures In Ansible

I have added the following tasks under the rescue directive which will do the cleanup. If any of the tasks in the block directive fails, the tasks under the rescue directive will run. 

rescue:
  - name: Remove Repository
    ansible.builtin.file:
      path: "/etc/apt/sources.list.d/docker.list"
      state: absent

  - name: Remove packages
    ansible.builtin.apt:
      pkg: ["docker-ce", "docker-ce-cli", "containerd.io", "docker-compose-plugin"]
      state: absent

I purposely gave the wrong package name in the playbook which will fail the second task and runs the rescue tasks. 

Ansible also provides two variables for failed tasks. You can use these variables to evaluate certain conditions in rescue or always tasks.

  1. ansible_failed_task.name -> Stores the failed task name
  2. ansible_failed_result -> This is similar to storing the task output in the register directive.
rescue:
  - name: print failed task name
    ansible.builtin.debug:
      var: ansible_failed_task.name

  - name: print failed task result
    ansible.builtin.debug:
      var: ansible_failed_result

Run Tasks Everytime Using The Always Directive

The tasks grouped and the always directive will always run irrespective of failures in either block or rescue tasks. I have added the follows always directive that has three tasks. The first and second tasks will check the docker and docker-compose version. The third task will print the versions.

always: 
  - name: Check Docker Version
    ansible.builtin.shell: docker --version
    register: dversion

  - name: Check Docker Compose Version
    ansible.builtin.shell: docker compose version
    register: dcversion

  - name: Print Docker Compose Version
    ansible.builtin.debug:
      msg:
        - "Docker version :: {{ dversion.stdout_lines }}"
        - "Docker compose version :: {{ dcversion.stdout_lines }}"\

In the below output first, the installation task is completed followed by the always tasks.

In real-time, you can use the always directive to send notifications to slack, email, etc. about the block and rescue task status.

Wrap Up

In this article, we have seen what is the use of block rescue and always directive with examples. We have also seen how to recover from failures using the rescue directive. We have also discussed how block-level tasks inherit other ansible directives like conditional statements, privilege escalations, etc.

Leave a Reply

fourteen + four =