Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Ansible templates and handlers

After this lesson: I can fill a template from variables and run a handler once.

60 min lesson14 commandsAnsible roles lab: a role, handlers and Vault on one Ubuntu VM

A template fills the gaps, a handler waits

An Ansible template is a text file with gaps that Ansible fills from variables, using a language called Jinja2. A handler is a task that runs only when another task changed something, and only once, however many tasks asked for it.

Together they let a role rewrite a settings file and restart a service only when the file really changed.

By the end of this lesson

Note

I can fill a template from variables and run a handler once.

Key words

WordWhat it meansExample
Templatea text file with gaps filled from variablesboard.txt.j2
Jinja2the language of the gaps, loops and conditions
Loopa for block that repeats lines for each item
Conditionan if block that keeps or drops lines
Handlera task that runs only when it is told to
Notifya task's request for a handler to run

The cells students fill in are left blank in this preview.

A template is a file with gaps

Double braces mark a gap: {{ shop_name }} is replaced by the variable's value. A line of the form {% for item in specials %} starts a loop, and {% endfor %} ends it.

A line of the form {% if late_opening %} keeps what follows only when the condition is true. The lines with {% in them make no output of their own.

A notice board template

Code: bash
# board.txt.j2
Notice board for {{ shop_name }}
{% for item in specials %}
- {{ item }}
{% endfor %}
{% if late_opening %}
Open late on Fridays
{% endif %}

Worked example: what the template makes

specialslate_openingLines the file gets
rye loaf, lemon tartfalseNotice board for Corner Shop, - rye loaf, - lemon tart
rye loaf, lemon tarttrueThe same three lines, then Open late on Fridays
nonefalseNotice board for Corner Shop

The cells students fill in are left blank in this preview.

Make the Ansible work folder

Command
mkdir -p ~/iac-demo/ansible

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Go into it

Command
cd ~/iac-demo/ansible

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Build the layout of a role called board

Command
ansible-galaxy role init --init-path roles board

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write its defaults

Command
printf '%s\n' '---' 'shop_name: Corner Shop' 'late_opening: false' 'specials:' '  - rye loaf' '  - lemon tart' > roles/board/defaults/main.yml

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the template shown above

Command
printf '%s\n' 'Notice board for {{ shop_name }}' '{% for item in specials %}' '- {{ item }}' '{% endfor %}' '{% if late_opening %}' 'Open late on Fridays' '{% endif %}' > roles/board/templates/board.txt.j2

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write two tasks that both notify a handler

Command
printf '%s\n' '---' '- name: Make the folder' '  ansible.builtin.file:' '    path: /tmp/iac-board' '    state: directory' '    mode: "0755"' '  notify: Tell the staff' '' '- name: Write the board' '  ansible.builtin.template:' '    src: board.txt.j2' '    dest: /tmp/iac-board/board.txt' '    mode: "0644"' '  notify: Tell the staff' > roles/board/tasks/main.yml

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the handler

Command
printf '%s\n' '---' '- name: Tell the staff' '  ansible.builtin.debug:' '    msg: The board has changed' > roles/board/handlers/main.yml

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write a play that uses the role

Command
printf '%s\n' '---' '- name: Keep the notice board' '  hosts: localhost' '  gather_facts: false' '  roles:' '    - board' > board.yml

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

A handler runs once, at the end

A task asks for a handler with notify. Ansible does not run the handler at once. It keeps a list of the handlers that were asked for, and runs each one once when the play ends.

A task that changed nothing asks for nothing. A task called meta: flush_handlers runs the waiting handlers early, for a role whose later tasks need the handler's work done first.

First run: both tasks change something

Command
ansible-playbook -i localhost, -c local board.yml | grep -oE "RUNNING HANDLER|changed=[0-9]+"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Read the file the template made

Command
cat /tmp/iac-board/board.txt

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Second run: nothing is different

Command
ansible-playbook -i localhost, -c local board.yml | grep -oE "RUNNING HANDLER|changed=[0-9]+"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

A common mistake

Warning

Many people think each notify runs the handler again, so three tasks mean three runs. Ansible collects the requests. At the end of the play it runs the handler one time, and only if a task really changed something.

Turn late opening on, using JSON

Command
ansible-playbook -i localhost, -c local board.yml -e '{"late_opening": true}' | grep -oE "RUNNING HANDLER|changed=[0-9]+"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Read the file again

Command
cat /tmp/iac-board/board.txt

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Text is not a Boolean

Important

A value typed as -e late_opening=false is the text false, and any text counts as true in a condition. To pass a real false, give JSON: -e '{"late_opening": false}'.

Dry run: show the line that would go

Command
ansible-playbook -i localhost, -c local board.yml -e '{"late_opening": false}' --check --diff | grep -E "^-[^-]|^\+[^+]"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

A template and a handler in Python

Code: python
# What the template makes, and when the handler runs.
specials = ["rye loaf", "lemon tart"]
late_opening = True
lines = ["Notice board for Corner Shop"]
for item in specials:
    lines.append("- " + item)
if late_opening:
    lines.append("Open late on Fridays")
print("\n".join(lines))
# A handler waits. It runs at a flush, and once more at the end if asked again.
steps = ["changed", "flush", "changed", "ok"]
waiting = False
runs = 0
for step in steps:
    if step == "changed":
        waiting = True
    elif step == "flush" and waiting:
        runs = runs + 1
        waiting = False
if waiting:
    runs = runs + 1
print("handler runs:", runs)
# Remove the flush from steps and run it again.

About this lab and its source: Ansible roles lab: a role, handlers and Vault on one Ubuntu VM

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.