Be Careful with the Order of Ansible Handlers

I recently stumbed across an gotcha with Ansible that I wasn't aware of. It happened when I was writing notification handlers that should run after a new version of code is downloaded to a server.

In my task file I was downloading (via Git) the latest code from the repository:

---
# roles/app-code/tasks/install_code.yml

- name: ensure code repository is downloaded
  git: >
    accept_hostkey=yes
    key_file={{ app_code_bitbucket_private_key_file }}
    repo={{ app_code_git_repository }}
    dest={{ app_code_home_dir }}
    version={{ app_code_git_version }}
  sudo: yes
  sudo_user: '{{ app_user_name }}'
  notify:
    - update gems
    - precompile assets
    - add hash marker file
    - restart app server

Whenever new code is downloaded to the system, the task will show CHANGED and each notification handler will be called. In this case, we want each notification to happen in a specific order because you don't want to restart the application server before the assets and third-party libraries are configured. In this case, using an array may not execute the handlers in this order even though you'd expect it to.

Let's look at the handlers file.

---
# roles/app-code/handlers/main.yml

- name: restart app server
  service: >
    name=appserver
    state=restarted
  sudo: yes

- name: update gems
  command: >
    bash -lc "bundle install --path={{ app_user_home_dir }}/.gem"
  args:
    chdir: '{{ app_code_home_dir }}'
  sudo: yes
  sudo_user: '{{ app_user_name }}'

- name: precompile assets
  shell: >
    rake assets:precompile
  args:
    chdir: "{{ app_code_home_dir }}"
  sudo: yes
  sudo_user: "{{ app_user_name }}"

When the notification handlers are run, you will see Ansible run them as:

  1. restart app server
  2. update gems
  3. precompile assets

This is because, buried in the Ansible glossary, it says:

Handlers are run in the order they are listed, not in the order that they are notified.

If you want to ensure that the handlers are executed in the correct order – or, at the very least, ensure that the app server is restarted last – the handlers file will need to be rewritten to:

---
# make this first
- name: update gems
  command: >
    bash -lc "bundle install --path={{ app_user_home_dir }}/.gem"
  args:
    chdir: '{{ app_code_home_dir }}'
  sudo: yes
  sudo_user: '{{ app_user_name }}'

# make this second
- name: precompile assets
  shell: >
    rake assets:precompile
  args:
    chdir: "{{ app_code_home_dir }}"
  sudo: yes
  sudo_user: "{{ app_user_name }}"

# make sure this is last
- name: restart app server
  service: >
    name=appserver
    state=restarted
  sudo: yes

Enjoy.