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:
- restart app server
- update gems
- 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.