1.. _playbooks_loops:
2
3*****
4Loops
5*****
6
7Ansible offers the ``loop``, ``with_<lookup>``, and ``until`` keywords to execute a task multiple times. Examples of commonly-used loops include changing ownership on several files and/or directories with the :ref:`file module <file_module>`, creating multiple users with the :ref:`user module <user_module>`, and
8repeating a polling step until a certain result is reached.
9
10.. note::
11   * We added ``loop`` in Ansible 2.5. It is not yet a full replacement for ``with_<lookup>``, but we recommend it for most use cases.
12   * We have not deprecated the use of ``with_<lookup>`` - that syntax will still be valid for the foreseeable future.
13   * We are looking to improve ``loop`` syntax - watch this page and the `changelog <https://github.com/ansible/ansible/tree/devel/changelogs>`_ for updates.
14
15.. contents::
16   :local:
17
18Comparing ``loop`` and ``with_*``
19=================================
20
21* The ``with_<lookup>`` keywords rely on :ref:`lookup_plugins` - even  ``items`` is a lookup.
22* The ``loop`` keyword is equivalent to ``with_list``, and is the best choice for simple loops.
23* The ``loop`` keyword will not accept a string as input, see :ref:`query_vs_lookup`.
24* Generally speaking, any use of ``with_*`` covered in :ref:`migrating_to_loop` can be updated to use ``loop``.
25* Be careful when changing ``with_items`` to ``loop``, as ``with_items`` performed implicit single-level flattening. You may need to use ``flatten(1)`` with ``loop`` to match the exact outcome. For example, to get the same output as:
26
27.. code-block:: yaml
28
29  with_items:
30    - 1
31    - [2,3]
32    - 4
33
34you would need::
35
36  loop: "{{ [1, [2,3] ,4] | flatten(1) }}"
37
38* Any ``with_*`` statement that requires using ``lookup`` within a loop should not be converted to use the ``loop`` keyword. For example, instead of doing:
39
40.. code-block:: yaml
41
42  loop: "{{ lookup('fileglob', '*.txt', wantlist=True) }}"
43
44it's cleaner to keep::
45
46  with_fileglob: '*.txt'
47
48.. _standard_loops:
49
50Standard loops
51==============
52
53Iterating over a simple list
54----------------------------
55
56Repeated tasks can be written as standard loops over a simple list of strings. You can define the list directly in the task::
57
58    - name: Add several users
59      ansible.builtin.user:
60        name: "{{ item }}"
61        state: present
62        groups: "wheel"
63      loop:
64         - testuser1
65         - testuser2
66
67You can define the list in a variables file, or in the 'vars' section of your play, then refer to the name of the list in the task::
68
69    loop: "{{ somelist }}"
70
71Either of these examples would be the equivalent of::
72
73    - name: Add user testuser1
74      ansible.builtin.user:
75        name: "testuser1"
76        state: present
77        groups: "wheel"
78
79    - name: Add user testuser2
80      ansible.builtin.user:
81        name: "testuser2"
82        state: present
83        groups: "wheel"
84
85You can pass a list directly to a parameter for some plugins. Most of the packaging modules, like :ref:`yum <yum_module>` and :ref:`apt <apt_module>`, have this capability. When available, passing the list to a parameter is better than looping over the task. For example::
86
87   - name: Optimal yum
88     ansible.builtin.yum:
89       name: "{{  list_of_packages  }}"
90       state: present
91
92   - name: Non-optimal yum, slower and may cause issues with interdependencies
93     ansible.builtin.yum:
94       name: "{{  item  }}"
95       state: present
96     loop: "{{  list_of_packages  }}"
97
98Check the :ref:`module documentation <modules_by_category>` to see if you can pass a list to any particular module's parameter(s).
99
100Iterating over a list of hashes
101-------------------------------
102
103If you have a list of hashes, you can reference subkeys in a loop. For example::
104
105    - name: Add several users
106      ansible.builtin.user:
107        name: "{{ item.name }}"
108        state: present
109        groups: "{{ item.groups }}"
110      loop:
111        - { name: 'testuser1', groups: 'wheel' }
112        - { name: 'testuser2', groups: 'root' }
113
114When combining :ref:`conditionals <playbooks_conditionals>` with a loop, the ``when:`` statement is processed separately for each item.
115See :ref:`the_when_statement` for examples.
116
117Iterating over a dictionary
118---------------------------
119
120To loop over a dict, use the  :ref:`dict2items <dict_filter>`:
121
122.. code-block:: yaml
123
124    - name: Using dict2items
125      ansible.builtin.debug:
126        msg: "{{ item.key }} - {{ item.value }}"
127      loop: "{{ tag_data | dict2items }}"
128      vars:
129        tag_data:
130          Environment: dev
131          Application: payment
132
133Here, we are iterating over `tag_data` and printing the key and the value from it.
134
135Registering variables with a loop
136=================================
137
138You can register the output of a loop as a variable. For example::
139
140   - name: Register loop output as a variable
141     ansible.builtin.shell: "echo {{ item }}"
142     loop:
143       - "one"
144       - "two"
145     register: echo
146
147When you use ``register`` with a loop, the data structure placed in the variable will contain a ``results`` attribute that is a list of all responses from the module. This differs from the data structure returned when using ``register`` without a loop::
148
149    {
150        "changed": true,
151        "msg": "All items completed",
152        "results": [
153            {
154                "changed": true,
155                "cmd": "echo \"one\" ",
156                "delta": "0:00:00.003110",
157                "end": "2013-12-19 12:00:05.187153",
158                "invocation": {
159                    "module_args": "echo \"one\"",
160                    "module_name": "shell"
161                },
162                "item": "one",
163                "rc": 0,
164                "start": "2013-12-19 12:00:05.184043",
165                "stderr": "",
166                "stdout": "one"
167            },
168            {
169                "changed": true,
170                "cmd": "echo \"two\" ",
171                "delta": "0:00:00.002920",
172                "end": "2013-12-19 12:00:05.245502",
173                "invocation": {
174                    "module_args": "echo \"two\"",
175                    "module_name": "shell"
176                },
177                "item": "two",
178                "rc": 0,
179                "start": "2013-12-19 12:00:05.242582",
180                "stderr": "",
181                "stdout": "two"
182            }
183        ]
184    }
185
186Subsequent loops over the registered variable to inspect the results may look like::
187
188    - name: Fail if return code is not 0
189      ansible.builtin.fail:
190        msg: "The command ({{ item.cmd }}) did not have a 0 return code"
191      when: item.rc != 0
192      loop: "{{ echo.results }}"
193
194During iteration, the result of the current item will be placed in the variable::
195
196    - name: Place the result of the current item in the variable
197      ansible.builtin.shell: echo "{{ item }}"
198      loop:
199        - one
200        - two
201      register: echo
202      changed_when: echo.stdout != "one"
203
204.. _complex_loops:
205
206Complex loops
207=============
208
209Iterating over nested lists
210---------------------------
211
212You can use Jinja2 expressions to iterate over complex lists. For example, a loop can combine nested lists::
213
214    - name: Give users access to multiple databases
215      community.mysql.mysql_user:
216        name: "{{ item[0] }}"
217        priv: "{{ item[1] }}.*:ALL"
218        append_privs: yes
219        password: "foo"
220      loop: "{{ ['alice', 'bob'] |product(['clientdb', 'employeedb', 'providerdb'])|list }}"
221
222
223.. _do_until_loops:
224
225Retrying a task until a condition is met
226----------------------------------------
227
228.. versionadded:: 1.4
229
230You can use the ``until`` keyword to retry a task until a certain condition is met.  Here's an example::
231
232    - name: Retry a task until a certain condition is met
233      ansible.builtin.shell: /usr/bin/foo
234      register: result
235      until: result.stdout.find("all systems go") != -1
236      retries: 5
237      delay: 10
238
239This task runs up to 5 times with a delay of 10 seconds between each attempt. If the result of any attempt has "all systems go" in its stdout, the task succeeds. The default value for "retries" is 3 and "delay" is 5.
240
241To see the results of individual retries, run the play with ``-vv``.
242
243When you run a task with ``until`` and register the result as a variable, the registered variable will include a key called "attempts", which records the number of the retries for the task.
244
245.. note:: You must set the ``until`` parameter if you want a task to retry. If ``until`` is not defined, the value for the ``retries`` parameter is forced to 1.
246
247Looping over inventory
248----------------------
249
250To loop over your inventory, or just a subset of it, you can use a regular ``loop`` with the ``ansible_play_batch`` or ``groups`` variables::
251
252    - name: Show all the hosts in the inventory
253      ansible.builtin.debug:
254        msg: "{{ item }}"
255      loop: "{{ groups['all'] }}"
256
257    - name: Show all the hosts in the current play
258      ansible.builtin.debug:
259        msg: "{{ item }}"
260      loop: "{{ ansible_play_batch }}"
261
262There is also a specific lookup plugin ``inventory_hostnames`` that can be used like this::
263
264    - name: Show all the hosts in the inventory
265      ansible.builtin.debug:
266        msg: "{{ item }}"
267      loop: "{{ query('inventory_hostnames', 'all') }}"
268
269    - name: Show all the hosts matching the pattern, ie all but the group www
270      ansible.builtin.debug:
271        msg: "{{ item }}"
272      loop: "{{ query('inventory_hostnames', 'all:!www') }}"
273
274More information on the patterns can be found in :ref:`intro_patterns`.
275
276.. _query_vs_lookup:
277
278Ensuring list input for ``loop``: using ``query`` rather than ``lookup``
279========================================================================
280
281The ``loop`` keyword requires a list as input, but the ``lookup`` keyword returns a string of comma-separated values by default. Ansible 2.5 introduced a new Jinja2 function named :ref:`query <query>` that always returns a list, offering a simpler interface and more predictable output from lookup plugins when using the ``loop`` keyword.
282
283You can force ``lookup`` to return a list to ``loop`` by using ``wantlist=True``, or you can use ``query`` instead.
284
285These examples do the same thing::
286
287    loop: "{{ query('inventory_hostnames', 'all') }}"
288
289    loop: "{{ lookup('inventory_hostnames', 'all', wantlist=True) }}"
290
291
292.. _loop_control:
293
294Adding controls to loops
295========================
296.. versionadded:: 2.1
297
298The ``loop_control`` keyword lets you manage your loops in useful ways.
299
300Limiting loop output with ``label``
301-----------------------------------
302.. versionadded:: 2.2
303
304When looping over complex data structures, the console output of your task can be enormous. To limit the displayed output, use the ``label`` directive with ``loop_control``::
305
306    - name: Create servers
307      digital_ocean:
308        name: "{{ item.name }}"
309        state: present
310      loop:
311        - name: server1
312          disks: 3gb
313          ram: 15Gb
314          network:
315            nic01: 100Gb
316            nic02: 10Gb
317            ...
318      loop_control:
319        label: "{{ item.name }}"
320
321The output of this task will display just the ``name`` field for each ``item`` instead of the entire contents of the multi-line ``{{ item }}`` variable.
322
323.. note:: This is for making console output more readable, not protecting sensitive data. If there is sensitive data in ``loop``, set ``no_log: yes`` on the task to prevent disclosure.
324
325Pausing within a loop
326---------------------
327.. versionadded:: 2.2
328
329To control the time (in seconds) between the execution of each item in a task loop, use the ``pause`` directive with ``loop_control``::
330
331    # main.yml
332    - name: Create servers, pause 3s before creating next
333      community.digitalocean.digital_ocean:
334        name: "{{ item }}"
335        state: present
336      loop:
337        - server1
338        - server2
339      loop_control:
340        pause: 3
341
342Tracking progress through a loop with ``index_var``
343---------------------------------------------------
344.. versionadded:: 2.5
345
346To keep track of where you are in a loop, use the ``index_var`` directive with ``loop_control``. This directive specifies a variable name to contain the current loop index::
347
348  - name: Count our fruit
349    ansible.builtin.debug:
350      msg: "{{ item }} with index {{ my_idx }}"
351    loop:
352      - apple
353      - banana
354      - pear
355    loop_control:
356      index_var: my_idx
357
358.. note:: `index_var` is 0 indexed.
359
360Defining inner and outer variable names with ``loop_var``
361---------------------------------------------------------
362.. versionadded:: 2.1
363
364You can nest two looping tasks using ``include_tasks``. However, by default Ansible sets the loop variable ``item`` for each loop. This means the inner, nested loop will overwrite the value of ``item`` from the outer loop.
365You can specify the name of the variable for each loop using ``loop_var`` with ``loop_control``::
366
367    # main.yml
368    - include_tasks: inner.yml
369      loop:
370        - 1
371        - 2
372        - 3
373      loop_control:
374        loop_var: outer_item
375
376    # inner.yml
377    - name: Print outer and inner items
378      ansible.builtin.debug:
379        msg: "outer item={{ outer_item }} inner item={{ item }}"
380      loop:
381        - a
382        - b
383        - c
384
385.. note:: If Ansible detects that the current loop is using a variable which has already been defined, it will raise an error to fail the task.
386
387Extended loop variables
388-----------------------
389.. versionadded:: 2.8
390
391As of Ansible 2.8 you can get extended loop information using the ``extended`` option to loop control. This option will expose the following information.
392
393==========================  ===========
394Variable                    Description
395--------------------------  -----------
396``ansible_loop.allitems``   The list of all items in the loop
397``ansible_loop.index``      The current iteration of the loop. (1 indexed)
398``ansible_loop.index0``     The current iteration of the loop. (0 indexed)
399``ansible_loop.revindex``   The number of iterations from the end of the loop (1 indexed)
400``ansible_loop.revindex0``  The number of iterations from the end of the loop (0 indexed)
401``ansible_loop.first``      ``True`` if first iteration
402``ansible_loop.last``       ``True`` if last iteration
403``ansible_loop.length``     The number of items in the loop
404``ansible_loop.previtem``   The item from the previous iteration of the loop. Undefined during the first iteration.
405``ansible_loop.nextitem``   The item from the following iteration of the loop. Undefined during the last iteration.
406==========================  ===========
407
408::
409
410      loop_control:
411        extended: yes
412
413Accessing the name of your loop_var
414-----------------------------------
415.. versionadded:: 2.8
416
417As of Ansible 2.8 you can get the name of the value provided to ``loop_control.loop_var`` using the ``ansible_loop_var`` variable
418
419For role authors, writing roles that allow loops, instead of dictating the required ``loop_var`` value, you can gather the value via::
420
421    "{{ lookup('vars', ansible_loop_var) }}"
422
423.. _migrating_to_loop:
424
425Migrating from with_X to loop
426=============================
427
428.. include:: shared_snippets/with2loop.txt
429
430.. seealso::
431
432   :ref:`about_playbooks`
433       An introduction to playbooks
434   :ref:`playbooks_reuse_roles`
435       Playbook organization by roles
436   :ref:`playbooks_best_practices`
437       Tips and tricks for playbooks
438   :ref:`playbooks_conditionals`
439       Conditional statements in playbooks
440   :ref:`playbooks_variables`
441       All about variables
442   `User Mailing List <https://groups.google.com/group/ansible-devel>`_
443       Have a question?  Stop by the google group!
444   `irc.libera.chat <https://libera.chat/>`_
445       #ansible IRC chat channel
446