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