Циклы
Ansible предлагает ключевые слова loop, with_<lookup>, и until для многократного выполнения задачи. Примеры часто используемых циклов включают изменение владельца нескольких файлов и/или каталогов с помощью модуля file, создание нескольких пользователей с помощью модуля user и повторение шага опроса до достижения определенного результата.
Примечание
- Мы добавили
loopв Ansible 2.5. Это пока не полная замена дляwith_<lookup>, но мы рекомендуем его для большинства случаев использования. - Мы не устарели
with_<lookup>- этот синтаксис будет оставаться действительным в обозримом будущем. - Мы стремимся улучшить синтаксис
loop- следите за этой страницей и журнал изменений для обновлений.
- Сравнение
loopиwith_* - Регистрация переменных с циклом
- Обеспечение списка входных данных для
loop: использованиеqueryвместоlookup
Сравнение loop и with_*
- Ключевые слова
with_<lookup>полагаются на Плагины поиска - дажеitemsэто поиск. - Ключевое слово
loopэквивалентноwith_list, и является лучшим выбором для простых циклов. - Ключевое слово
loopне будет принимать строку в качестве входных данных, см. Обеспечение списка входных данных для цикла: использование query вместо lookup. - В общем случае любое использование
with_*, охватываемое в Миграция с with_X на loop, может быть обновлено для использованияloop - Будьте осторожны при изменении
with_itemsнаloop, так какwith_itemsвыполнял неявное сглаживание на один уровень. Возможно, вам потребуется использоватьflatten(1)сloopдля соответствия точному результату. Например, чтобы получить тот же вывод, что и:
with_items: - 1 - [2,3] - 4
вам потребуется:
loop: "{{ [1, [2,3] ,4] | flatten(1) }}"
- Любое утверждение
with_*, которое требует использованияlookupвнутри цикла, не должно быть преобразовано для использования ключевого словаloop. Например, вместо:
loop: "{{ lookup('fileglob', '*.txt', wantlist=True) }}"
лучше сохранить:
with_fileglob: '*.txt'
Стандартные циклы
Итерация по простому списку
Повторные задачи могут быть записаны как стандартные циклы по простому списку строк. Вы можете определить список непосредственно в задаче:
- name: Add several users
ansible.builtin.user:
name: "{{ item }}"
state: present
groups: "wheel"
loop:
- testuser1
- testuser2
Вы можете определить список в файле переменных или в разделе 'vars' вашей книги задач, затем сослаться на имя списка в задаче:
loop: "{{ somelist }}"
Любой из этих примеров был бы эквивалентен:
- name: Add user testuser1
ansible.builtin.user:
name: "testuser1"
state: present
groups: "wheel"
- name: Add user testuser2
ansible.builtin.user:
name: "testuser2"
state: present
groups: "wheel"
Вы можете передать список непосредственно в параметр для некоторых плагинов. Большинство модулей пакетирования, таких как yum и apt, обладают такой возможностью. Когда это возможно, передача списка в параметр лучше, чем итерация по задаче. Например:
- name: Optimal yum
ansible.builtin.yum:
name: "{{ list_of_packages }}"
state: present
- name: Non-optimal yum, slower and may cause issues with interdependencies
ansible.builtin.yum:
name: "{{ item }}"
state: present
loop: "{{ list_of_packages }}"
Проверьте документацию модулей, чтобы узнать, можете ли вы передать список в параметр(ы) конкретного модуля.
Итерация по списку хешей
Если у вас есть список хешей, вы можете сослаться на подключаемые ключи в цикле. Например:
- name: Add several users
ansible.builtin.user:
name: "{{ item.name }}"
state: present
groups: "{{ item.groups }}"
loop:
- { name: 'testuser1', groups: 'wheel' }
- { name: 'testuser2', groups: 'root' }
При объединении условий с циклом, оператор when: обрабатывается отдельно для каждого элемента. См. Основные условные операторы с когда для примеров.
Итерация по словарю
Для итерации по словарю используйте dict2items:
- name: Using dict2items
ansible.builtin.debug:
msg: "{{ item.key }} - {{ item.value }}"
loop: "{{ tag_data | dict2items }}"
vars:
tag_data:
Environment: dev
Application: payment
Здесь мы итерируем по tag_data и выводим ключ и значение из него.
Регистрация переменных с циклом
Вы можете зарегистрировать вывод цикла как переменную. Например:
- name: Register loop output as a variable
ansible.builtin.shell: "echo {{ item }}"
loop:
- "one"
- "two"
register: echo
Когда вы используете register с циклом, структура данных, помещенная в переменную, будет содержать атрибут results, который является списком всех ответов из модуля. Это отличается от структуры данных, возвращаемой при использовании register без цикла:
{
"changed": true,
"msg": "All items completed",
"results": [
{
"changed": true,
"cmd": "echo \"one\" ",
"delta": "0:00:00.003110",
"end": "2013-12-19 12:00:05.187153",
"invocation": {
"module_args": "echo \"one\"",
"module_name": "shell"
},
"item": "one",
"rc": 0,
"start": "2013-12-19 12:00:05.184043",
"stderr": "",
"stdout": "one"
},
{
"changed": true,
"cmd": "echo \"two\" ",
"delta": "0:00:00.002920",
"end": "2013-12-19 12:00:05.245502",
"invocation": {
"module_args": "echo \"two\"",
"module_name": "shell"
},
"item": "two",
"rc": 0,
"start": "2013-12-19 12:00:05.242582",
"stderr": "",
"stdout": "two"
}
]
}
Последующие циклы по зарегистрированной переменной для проверки результатов могут выглядеть так:
- name: Fail if return code is not 0
ansible.builtin.fail:
msg: "The command ({{ item.cmd }}) did not have a 0 return code"
when: item.rc != 0
loop: "{{ echo.results }}"
Во время итерации результат текущего элемента будет помещен в переменную:
- name: Place the result of the current item in the variable
ansible.builtin.shell: echo "{{ item }}"
loop:
- one
- two
register: echo
changed_when: echo.stdout != "one"
Сложные циклы
Итерация по вложенным спискам
Вы можете использовать выражения Jinja2 для итерации по сложным спискам. Например, цикл может объединять вложенные списки:
- name: Give users access to multiple databases
community.mysql.mysql_user:
name: "{{ item[0] }}"
priv: "{{ item[1] }}.*:ALL"
append_privs: yes
password: "foo"
loop: "{{ ['alice', 'bob'] |product(['clientdb', 'employeedb', 'providerdb'])|list }}"
Повторное выполнение задачи до достижения условия
Новое в версии 1.4.
Вы можете использовать ключевое слово until для повторного выполнения задачи до достижения определенного условия. Вот пример:
- name: Retry a task until a certain condition is met
ansible.builtin.shell: /usr/bin/foo
register: result
until: result.stdout.find("all systems go") != -1
retries: 5
delay: 10
Эта задача выполняется до 5 раз с задержкой в 10 секунд между каждой попыткой. Если результат любой попытки содержит «все системы работают» в своем стандартном выводе, задача выполняется успешно. Значение по умолчанию для «повторов» равно 3, а «задержка» равна 5.
Чтобы увидеть результаты отдельных повторов, выполните книгу задач с -vv.
Когда вы выполняете задачу с until и регистрируете результат как переменную, зарегистрированная переменная будет содержать ключ «попытки», который записывает количество повторов для задачи.
Примечание
Вы должны установить параметр until , если хотите, чтобы задача повторялась. Если until не определен, значение для параметра retries принудительно устанавливается в 1.
Итерация по инвентаризации
Чтобы итерировать по вашей инвентаризации или только по подмножеству, вы можете использовать обычный loop с переменными ansible_play_batch или groups
- name: Show all the hosts in the inventory
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ groups['all'] }}"
- name: Show all the hosts in the current play
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ ansible_play_batch }}"
Также существует специальный плагин поиска inventory_hostnames, который можно использовать так:
- name: Show all the hosts in the inventory
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ query('inventory_hostnames', 'all') }}"
- name: Show all the hosts matching the pattern, ie all but the group www
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ query('inventory_hostnames', 'all:!www') }}"
Дополнительную информацию о шаблонах можно найти в Шаблоны: целевые узлы и группы.
Обеспечение списка входных данных для loop: использование query вместо lookup
Ключевое слово loop требует список в качестве входных данных, но ключевое слово lookup по умолчанию возвращает строку с запятыми, разделяющими значения. Ansible 2.5 представил новую функцию Jinja2 под названием query, которая всегда возвращает список, предлагая более простой интерфейс и более предсказуемый вывод плагинов lookup при использовании ключевого слова loop.
Вы можете принудительно заставить lookup возвращать список в loop с помощью wantlist=True, или вы можете использовать query вместо этого.
Эти примеры делают одно и то же:
loop: "{{ query('inventory_hostnames', 'all') }}"
loop: "{{ lookup('inventory_hostnames', 'all', wantlist=True) }}"
Добавление управления циклами
Новое в версии 2.1.
Ключевое слово loop_control позволяет управлять вашими циклами полезными способами.
Ограничение вывода цикла с помощью label
Новое в версии 2.2.
При итерации по сложным структурам данных вывод задачи в консоли может быть очень большим. Чтобы ограничить отображаемый вывод, используйте директиву label с loop_control.
- name: Create servers
digital_ocean:
name: "{{ item.name }}"
state: present
loop:
- name: server1
disks: 3gb
ram: 15Gb
network:
nic01: 100Gb
nic02: 10Gb
...
loop_control:
label: "{{ item.name }}"
Вывод этой задачи отобразит только поле name для каждого item вместо всего содержимого многострочной переменной {{ item }}.
Примечание
Это для повышения удобочитаемости вывода в консоли, а не для защиты конфиденциальных данных. Если в loop содержатся конфиденциальные данные, установите no_log: yes в задаче, чтобы предотвратить раскрытие.
Пауза внутри цикла
Новое в версии 2.2.
Чтобы контролировать время (в секундах) между выполнением каждого элемента в цикле задачи, используйте директиву pause с loop_control:
# main.yml
- name: Create servers, pause 3s before creating next
community.digitalocean.digital_ocean:
name: "{{ item }}"
state: present
loop:
- server1
- server2
loop_control:
pause: 3
Отслеживание прогресса по циклу с помощью index_var
Новое в версии 2.5.
Чтобы отслеживать текущую позицию в цикле, используйте директиву index_var с loop_control. Эта директива определяет имя переменной, которая будет содержать текущий индекс цикла:
- name: Count our fruit
ansible.builtin.debug:
msg: "{{ item }} with index {{ my_idx }}"
loop:
- apple
- banana
- pear
loop_control:
index_var: my_idx
Примечание
index_var индексируется с 0.
Определение имен внутренних и внешних переменных с loop_var
Новое в версии 2.1.
Вы можете вложить две задачи цикла с помощью include_tasks. Однако по умолчанию Ansible устанавливает переменную цикла item для каждого цикла. Это означает, что внутренний, вложенный цикл перезапишет значение item из внешнего цикла. Вы можете указать имя переменной для каждого цикла, используя loop_var с loop_control:
# main.yml
- include_tasks: inner.yml
loop:
- 1
- 2
- 3
loop_control:
loop_var: outer_item
# inner.yml
- name: Print outer and inner items
ansible.builtin.debug:
msg: "outer item={{ outer_item }} inner item={{ item }}"
loop:
- a
- b
- c
Примечание
Если Ansible обнаружит, что текущий цикл использует переменную, которая уже определена, он выведет ошибку, чтобы прервать задачу.
Расширенные переменные цикла
Новое в версии 2.8.
Начиная с Ansible 2.8, вы можете получить расширенную информацию о цикле, используя опцию extended для управления циклом. Эта опция предоставит следующую информацию.
Переменная | Описание |
| Список всех элементов в цикле |
| Текущая итерация цикла. (Индексируется с 1) |
| Текущая итерация цикла. (Индексируется с 0) |
| Количество итераций от конца цикла (индексируется с 1) |
| Количество итераций от конца цикла (индексируется с 0) |
|
|
|
|
| Количество элементов в цикле |
| Элемент из предыдущей итерации цикла. Не определено во время первой итерации. |
| Элемент из следующей итерации цикла. Не определено во время последней итерации. |
loop_control: extended: yes
Доступ к имени вашей переменной цикла
Новое в версии 2.8.
Начиная с Ansible 2.8, вы можете получить имя значения, переданного в loop_control.loop_var, используя переменную ansible_loop_var
Для авторов ролей, создающих роли, которые допускают циклы, вместо предписывания необходимого значения loop_var, вы можете получить значение следующим образом:
"{{ lookup('vars', ansible_loop_var) }}"
Миграция с with_X на loop
В большинстве случаев циклы работают лучше с ключевым словом loop вместо циклов стиля with_X. Синтаксис loop обычно лучше выражать с помощью фильтров, а не с более сложным использованием query или lookup.
Эти примеры показывают, как преобразовать многие распространенные циклы стиля with_ в loop и фильтры.
with_list
with_list напрямую заменяется на loop.
- name: with_list
ansible.builtin.debug:
msg: "{{ item }}"
with_list:
- one
- two
- name: with_list -> loop
ansible.builtin.debug:
msg: "{{ item }}"
loop:
- one
- two
with_items
with_items заменяется на loop и фильтр flatten.
- name: with_items
ansible.builtin.debug:
msg: "{{ item }}"
with_items: "{{ items }}"
- name: with_items -> loop
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ items|flatten(levels=1) }}"
with_indexed_items
with_indexed_items заменяется на loop, фильтр flatten и loop_control.index_var.
- name: with_indexed_items
ansible.builtin.debug:
msg: "{{ item.0 }} - {{ item.1 }}"
with_indexed_items: "{{ items }}"
- name: with_indexed_items -> loop
ansible.builtin.debug:
msg: "{{ index }} - {{ item }}"
loop: "{{ items|flatten(levels=1) }}"
loop_control:
index_var: index
with_flattened
with_flattened заменяется на loop и фильтр flatten.
- name: with_flattened
ansible.builtin.debug:
msg: "{{ item }}"
with_flattened: "{{ items }}"
- name: with_flattened -> loop
ansible.builtin.debug:
msg: "{{ item }}"
loop: "{{ items|flatten }}"
with_together
with_together заменяется на loop и фильтр zip.
- name: with_together
ansible.builtin.debug:
msg: "{{ item.0 }} - {{ item.1 }}"
with_together:
- "{{ list_one }}"
- "{{ list_two }}"
- name: with_together -> loop
ansible.builtin.debug:
msg: "{{ item.0 }} - {{ item.1 }}"
loop: "{{ list_one|zip(list_two)|list }}"
Еще один пример со сложными данными
- name: with_together -> loop
ansible.builtin.debug:
msg: "{{ item.0 }} - {{ item.1 }} - {{ item.2 }}"
loop: "{{ data[0]|zip(*data[1:])|list }}"
vars:
data:
- ['a', 'b', 'c']
- ['d', 'e', 'f']
- ['g', 'h', 'i']
with_dict
with_dict может быть заменено на loop и либо фильтр dictsort, либо dict2items.
- name: with_dict
ansible.builtin.debug:
msg: "{{ item.key }} - {{ item.value }}"
with_dict: "{{ dictionary }}"
- name: with_dict -> loop (option 1)
ansible.builtin.debug:
msg: "{{ item.key }} - {{ item.value }}"
loop: "{{ dictionary|dict2items }}"
- name: with_dict -> loop (option 2)
ansible.builtin.debug:
msg: "{{ item.0 }} - {{ item.1 }}"
loop: "{{ dictionary|dictsort }}"
with_sequence
with_sequence заменяется на loop и функцию range, и потенциально фильтр format.
- name: with_sequence
ansible.builtin.debug:
msg: "{{ item }}"
with_sequence: start=0 end=4 stride=2 format=testuser%02x
- name: with_sequence -> loop
ansible.builtin.debug:
msg: "{{ 'testuser%02x' | format(item) }}"
# range is exclusive of the end point
loop: "{{ range(0, 4 + 1, 2)|list }}"
with_subelements
with_subelements заменяется на loop и фильтр subelements.
- name: with_subelements
ansible.builtin.debug:
msg: "{{ item.0.name }} - {{ item.1 }}"
with_subelements:
- "{{ users }}"
- mysql.hosts
- name: with_subelements -> loop
ansible.builtin.debug:
msg: "{{ item.0.name }} - {{ item.1 }}"
loop: "{{ users|subelements('mysql.hosts') }}"
with_nested/with_cartesian
with_nested и with_cartesian заменяются на цикл и фильтр product.
- name: with_nested
ansible.builtin.debug:
msg: "{{ item.0 }} - {{ item.1 }}"
with_nested:
- "{{ list_one }}"
- "{{ list_two }}"
- name: with_nested -> loop
ansible.builtin.debug:
msg: "{{ item.0 }} - {{ item.1 }}"
loop: "{{ list_one|product(list_two)|list }}"
with_random_choice
with_random_choice заменяется на использование только фильтра random, без необходимости в loop.
- name: with_random_choice
ansible.builtin.debug:
msg: "{{ item }}"
with_random_choice: "{{ my_list }}"
- name: with_random_choice -> loop (No loop is needed here)
ansible.builtin.debug:
msg: "{{ my_list|random }}"
tags: random
См. также
- Введение в playbook
-
Введение в playbook
- Роли
-
Организация playbook по ролям
- Советы и рекомендации
-
Советы и рекомендации для playbooks
- Условные операторы
-
Условные операторы в playbooks
- Использование переменных
-
Все о переменных
- Пользовательский список рассылки
-
Есть вопрос? Заходите на форум!
- irc.freenode.net
-
IRC-чат-канал #ansible
© 2012–2018 Michael DeHaan
© 2018–2021 Red Hat, Inc.
Licensed under the GNU General Public License version 3.
https://docs.ansible.com/ansible/2.11/user_guide/playbooks_loops.html