Ansible loops & conditionals.
Two tiny keywords carry most of Ansible's logic: `loop` runs a task once per element (exposed as {{ item }}), and `when` decides β per host and per loop item β whether it runs at all. Then teach command/shell what `changed` and `failed` really mean.
Ansible loops and conditionals animated tutorial. Looping a task over a list with loop and the item variable, replacing legacy with_items, when conditionals as bare Jinja expressions, combining loop with when to skip iterations per item, registering looped tasks and reading result.results, changed_when and failed_when for non-idempotent command and shell tasks, loop_control with label and index_var, and until/retries/delay polling. RHCE EX294 ready.Two tiny keywords carry most of Ansible's logic: loop runs a task once per element, and when decides whether it runs at all. Watch a task iterate over users, get gated per host, and learn what "changed" really means.
- Β·You've written a basic playbook with tasks
- Β·Comfortable with YAML lists and dictionaries
- Β·Know what a fact / variable is in Ansible
Loop a task over a list with {{ item }}, gate tasks with when:, combine the two to skip iterations, redefine changed/failed for command tasks, and poll with until/retries.
pace: 9 minutes
- name: Create usertaskitem=aliceβ changeditem=bobβ changeditem=carolβ changedloop β do one task many times
Add `loop:` with a list and Ansible runs the task once per element, exposing the current element as `{{ item }}`. Modern `loop:` replaces the older `with_items` and friends. You can loop over a plain list, a variable that holds a list, or a list of dictionaries β then reach into each dict with `{{ item.name }}`. One task definition, many runs.
$- name: Create user\n ansible.builtin.user:\n name: "{{ item }}"\n loop: [alice, bob, carol]$loop: "{{ users }}"$- ansible.builtin.user:\n name: "{{ item.name }}"\n groups: "{{ item.groups }}"\n loop: "{{ user_list }}"Two small keywords carry most of Ansible's logic: `loop` runs a task once per element (the current one is {{ item }}), and `when` decides per host (and per loop item) whether it runs at all. And because command/shell aren't idempotent, `changed_when`/`failed_when` let you teach Ansible what "changed" and "failed" actually mean for them.
$loop: [a, b, c]$loop: "{{ list }}"${{ item }} / {{ item.key }}$with_items: [β¦]$when: COND$when: a and b$when: x is defined$when:\n - c1\n - c2$register: r β r.results$changed_when: false$failed_when: COND$loop_control: label / index_var / pause$until: COND retries: N delay: S$loop: "{{ d | dict2items }}"Do you wrap a `when:` condition in `{{ }}`?
How do you make a one-off `command` task never report "changed"?
You loop over 3 users and `register` the result. Where are the per-item results?
What does `until` + `retries` + `delay` do?
These aren't graded β they're just for active recall, which is what actually makes the lesson stick.
Ansible templates (Jinja2)
Loops and conditionals also live inside config files. Render per-host config with the `template` module and Jinja2 β variables, loops, and `when`-style logic, straight into /etc.
Read this lesson as text5 chapters Β· quiz answers
01loop β do one task many times
Add
loop:with a list and Ansible runs the task once per element, exposing the current element as{{ item }}. Modernloop:replaces the olderwith_itemsand friends. You can loop over a plain list, a variable that holds a list, or a list of dictionaries β then reach into each dict with{{ item.name }}. One task definition, many runs.$ - name: Create user\n ansible.builtin.user:\n name: "{{ item }}"\n loop: [alice, bob, carol]β one task, three runs$ loop: "{{ users }}"β loop a variable that holds a list$ - ansible.builtin.user:\n name: "{{ item.name }}"\n groups: "{{ item.groups }}"\n loop: "{{ user_list }}"β list of dicts β item.name
02when β run only if a condition holds
when:takes a RAW Jinja expression β you do NOT wrap the whole condition in{{ }}(braces are only for inserting a value into a string). Combine tests withand/or, or give a YAML list, which is an implicit AND. You can test facts, variables,is defined, and comparisons. Here the gateos_family == "RedHat"decides whether the task runs at all on a given host.$ when: ansible_facts['os_family'] == "RedHat"β bare expression β no {{ }}$ when: my_var is definedβ test that a var exists$ when:\n - ansible_facts['os_family'] == "RedHat"\n - install_web | boolβ a YAML list = implicit AND
03loop + when together
Put
when:on a looped task and the condition is evaluated PER ITEM β so some iterations run and others are skipped, all from one task. Looping over a list of dicts lets each element carry its own flag, e.g.when: item.enabled. If youregister:a looped task you don't get a single result β you getresult.results, a list with one entry per item that you can iterate later.$ - ansible.builtin.user:\n name: "{{ item.name }}"\n loop: "{{ users }}"\n when: item.enabledβ skip per item$ - ansible.builtin.command: "id {{ item }}"\n loop: [alice, bob, carol]\n register: resultβ register a looped task$ - debug:\n var: item.stdout\n loop: "{{ result.results }}"β result.results = one per item
04changed_when / failed_when β redefine the outcome
commandandshellaren't idempotent, so Ansible can't tell if they really changed anything β by default they always reportchanged. Usechanged_when:to override that (oftenchanged_when: falsefor a read-only check) andfailed_when:to decide what counts as a failure based on the registered result. These take the same bare Jinja expressions aswhen:.$ - ansible.builtin.command: systemctl is-active sshd\n changed_when: falseβ a check never "changes"$ - ansible.builtin.command: /opt/build.sh\n register: result\n failed_when: "'ERROR' in result.stderr"β fail on a string in output$ failed_when: result.rc != 0 and result.rc != 2β rc 2 is fine here
05loop controls & retries
loop_control:cleans up looped tasks:labelshows a friendly string instead of the whole dict in output,index_varexposes the position, andpausewaits between items. To poll until something is ready, adduntil:withretries:anddelay:β Ansible re-runs the task until the condition is true or it runs out of attempts. Usedict2itemsto loop over a dictionary as key/value pairs.$ - ansible.builtin.user:\n name: "{{ item.name }}"\n loop: "{{ users }}"\n loop_control:\n label: "{{ item.name }}"β tidy output via label$ - ansible.builtin.command: curl -sf http://localhost\n register: result\n until: result.rc == 0\n retries: 5\n delay: 3β poll until rc 0$ loop: "{{ my_dict | dict2items }}"β iterate a dict as key/value
Quiz β answers
- Do you wrap a
when:condition in{{ }}? - βNo β
whentakes a bare Jinja expression; the{{ }}braces are only for inserting a value into a string.when:is already evaluated as a Jinja expression, so you writewhen: ansible_facts['os_family'] == "RedHat"with no braces. Braces are for templating a value into a string, e.g. name: "{{ item }}". - How do you make a one-off
commandtask never report "changed"? - βSet
changed_when: false.command/shellaren't idempotent, so they default to reporting changed every run.changed_when: falsetells Ansible this task is a read-only check that never changes the system. - You loop over 3 users and
registerthe result. Where are the per-item results? - βIn
result.resultsβ a list with one entry per loop item.A registered looped task gives youresult.results, a list with one entry per iteration (each holding that item's own rc, stdout, item value, etc). You loop overresult.resultsto use them. - What does
until+retries+delaydo? - βRetries the task until the
untilcondition is true, up toretriestimes, waitingdelayseconds between attempts β polling.It's a polling loop: Ansible re-runs the task, checksuntil, and if it's still false waitsdelayseconds and tries again, giving up afterretriesattempts. Great for waiting on a service to come up.
keep going β these pair well with what you just learned.
Ansible control node & inventory
Ansible is agentless β one control node pushes work to a fleet over SSH. Install ansible-core, write an inventory with groups, ping the fleet, and fire ad-hoc commands. RHCE-ready.
Ansible playbooks & YAML
Plays, tasks, and idempotency. Watch a playbook run task-by-task β ok, changed, skipped β then run it again and watch everything go green with 0 changed. RHCE-ready.
Ansible core modules
file, user, copy, dnf, service β the modules that do the real work. Declare the desired state and let Ansible converge to it, idempotently. RHCE-ready.