Expert · Ansible in depth · topic 50 of 57 · 35 min
Loops, conditions and error handling
Explanation
Loops
- name: Install tools
ansible.builtin.dnf:
name: "{{ item }}"
state: present
loop:
- vim-enhanced
- tmux
- git# vars
users:
- name: asha
groups: wheel
- name: ravi
groups: developers
- name: meena
groups: developers
# task
- name: Create users
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
append: true
loop: "{{ users }}"
loop_control:
label: "{{ item.name }}"Conditions with when
when takes a Jinja2 expression WITHOUT the {{ }} braces. If it is false, the task is skipped for that host (shown as "skipping"). Combine with and, or, not and brackets; a list under when means all must be true.
- name: Install Apache only on RHEL 9
ansible.builtin.dnf:
name: httpd
state: present
when:
- ansible_facts['distribution'] == "RedHat"
- ansible_facts['distribution_major_version'] == "9"
- name: Warn about small servers
ansible.builtin.debug:
msg: "{{ inventory_hostname }} has only {{ ansible_facts['memtotal_mb'] }} MB RAM"
when: ansible_facts['memtotal_mb'] < 2048
- name: Only for hosts in the dbservers group
ansible.builtin.dnf:
name: mariadb-server
when: "'dbservers' in group_names"With a loop, when is checked for each item: when: item.groups == "wheel" creates only the admins. To test a registered result: when: result is failed, when: result.rc != 0, or when: conf.stat.exists.
Deciding what failed and what changed
- name: Check the app health page
ansible.builtin.uri:
url: http://localhost:8080/healthz
status_code: [200, 503]
register: health
- name: Is the database reachable?
ansible.builtin.command: /opt/notes/bin/check-db
register: dbcheck
changed_when: false
failed_when: "'unreachable' in dbcheck.stdout"block, rescue and always
- name: Upgrade the app safely
block:
- name: Stop the app
ansible.builtin.service: { name: notes, state: stopped }
- name: Install the new version
ansible.builtin.dnf: { name: notes-2.1, state: present }
rescue:
- name: Roll back to the old version
ansible.builtin.dnf: { name: notes-2.0, state: present, allow_downgrade: true }
always:
- name: Start the app again
ansible.builtin.service: { name: notes, state: started }ignore_errors: true on a task lets the play continue even if that task fails. Use it sparingly; it hides real problems. A block with rescue is usually the cleaner way.
Retrying until something is ready
Sometimes a task must wait, for example until a freshly restarted app answers on its port. until repeats the task until a condition is true, trying up to retries times with delay seconds in between. If the condition never becomes true, the task fails with a clear message instead of the next task failing for a confusing reason.
- name: Wait until the app answers
ansible.builtin.uri:
url: http://localhost:8080/healthz
register: health
until: health.status == 200
retries: 10
delay: 5Common mistakes
- Putting braces in when: when: "{{ x }} == 1". Ansible warns; write when: x == 1.
- Comparing version facts as numbers without | int.
- Using the old with_items syntax in new code. loop is the current way (with_* still works).
- A loop over a big list of packages with dnf, which is slow; pass the list to name instead.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Loops, conditions and error handling · 1/6
loop:
- vim-enhanced
- tmux
name: "{{ item }}"loop repeats a task for each item. The current item is item.
Prefer a video by a teacher? Find free videos on “ansible loops when conditionals block rescue always tutorial” on YouTube ↗
In real life: Mixed fleet at an Indian railway IT vendor
An IT vendor maintaining ticketing kiosks for a railway zone has RHEL 8 and RHEL 9 machines in the field. Their playbook uses when on distribution_major_version to pick the right package names for each, loops over a list of kiosk users, and wraps the kiosk software upgrade in a block with a rescue that reinstalls the previous version, so a failed upgrade never leaves a station without a working kiosk.
- name: Kiosk browser for RHEL 8
ansible.builtin.dnf:
name: kiosk-browser-1.8
when: ansible_facts['distribution_major_version'] == "8"
- name: Kiosk browser for RHEL 9
ansible.builtin.dnf:
name: kiosk-browser-2.3
when: ansible_facts['distribution_major_version'] == "9"Practice (0/3 done)
Which when line is written correctly?
Model a loop with a condition. users is a list of dicts with name, state ("present" or "absent") and optional groups. Write plan(users, only_group=None) that returns, in order, one string per user that a task with loop + when would act on: "create NAME" for present users and "remove NAME" for absent ones. If only_group is given, include only users whose groups list contains it.
Write users.yml: a variable list of four users (name, groups, state). Create the groups first with a loop (unique group names), then create or remove users with one loop task using when to pick present/absent. Add a block that installs a package that does not exist, with a rescue that prints a friendly message and an always that writes "done" to /tmp/users.log.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Jinja2 templatesAnsible in depth · same module
- Roles, collections and ansible-galaxyAnsible in depth · same module
- Loops: repeating workFrontend Web Development · JavaScript basics
- while loopsPython Programming · Loops: repeating work
- Control flow: if, loop, while and forRust Programming · Types, control flow and functions
- Error handling: try, catch, finally and throwJavaScript Programming (language in depth) · Modules, errors, JSON and dates
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
In a loop, what variable holds the current element?
Answer: item
item (rename with loop_control: loop_var).
What does Ansible show for a task whose when is false?
Answer: skipping
It is skipped for that host.
When does the always section of a block run?
Answer: Every time, after block (and rescue if it ran)
Like finally.
Why is name: "{{ packages }}" (a list) better than a loop for dnf?
Answer: One dnf transaction is faster than one per package
dnf accepts a list.
Which loop is best to visit every item of an array?Related
Answer: for...of
for...of gives you each item in turn, with no index to manage.
Which loop fits best when you repeat until something happens, without knowing how many rounds?Related
Answer: while
A while loop keeps going until its condition becomes False, however many rounds that takes.
From while loops
Why add changed_when: false to a command that only reads data?Related
Answer: So it does not report "changed" every run
Reading is not a change; this keeps the recap honest.
Which loop is safest for going over every item of an array?Related
Answer: for item in array
for cannot go past the end, so there are no index mistakes.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 3 more topics in “Ansible in depth” before its exam: Jinja2 templates, Roles, collections and ansible-galaxy, RHEL System Roles and Ansible Vault.
- Module 10 exam: Ansible in depth · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Certifications and career. 57 topics left in the course before the final exam.