Циклы
Иногда необходимо повторить задачу несколько раз. В программировании это называется циклом. Общие циклы в Ansible включают изменение владельца нескольких файлов и/или каталогов с помощью модуля file, создание нескольких пользователей с помощью модуля user и повторение шага опроса до достижения определенного результата. Ansible предлагает два ключевых слова для создания циклов: loop и with_<lookup>.
Примечание
- Мы добавили
loopв Ansible 2.5. Это еще не полная заменаwith_<lookup>, но мы рекомендуем его для большинства случаев использования. - Мы не устарели использование
with_<lookup>— этот синтаксис останется допустимым в обозримом будущем. - Мы стремимся улучшить синтаксис
loop— следите за этой страницей и журналами изменений для обновлений.
- Сравнение
loopиwith_* - Стандартные циклы
- Регистрация переменных с циклом
- Сложные циклы
- Обеспечение ввода списка для
loop:queryпротивlookup - Добавление управления в циклы
- Миграция из with_X в loop
Сравнение loop и with_*
- Ключевые слова
with_<lookup>полагаются на Плагины Lookup — дажеitemsявляется плагином lookup. - Ключевое слово
loopэквивалентноwith_list, и является лучшим выбором для простых циклов. - Ключевое слово
loopне будет принимать строку в качестве входных данных, см. Обеспечение ввода списка для цикла: запрос против поиска. - В общем случае любое использование
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
user:
name: "{{ item }}"
state: present
groups: "wheel"
loop:
- testuser1
- testuser2
Вы можете определить список в файле переменных или в разделе «переменные» вашего плейлиста, а затем обратиться к имени списка в задаче:
loop: "{{ somelist }}"
Любой из этих примеров эквивалентен:
- name: add user testuser1
user:
name: "testuser1"
state: present
groups: "wheel"
- name: add user testuser2
user:
name: "testuser2"
state: present
groups: "wheel"
Вы можете передать список непосредственно в параметр для некоторых плагинов. Большинство модулей упаковки, таких как yum — Управляет пакетами с помощью менеджера пакетов yum и apt — Управляет пакетами apt, имеют эту возможность. При наличии, передача списка в параметр лучше, чем проход по задаче в цикле. Например:
- name: optimal yum
yum:
name: "{{ list_of_packages }}"
state: present
- name: non-optimal yum, slower and may cause issues with interdependencies
yum:
name: "{{ item }}"
state: present
loop: "{{ list_of_packages }}"
Проверьте документацию по модулям, чтобы узнать, можно ли передать список в параметр(ы) конкретного модуля.
Итерация по списку словарей
Если у вас есть список словарей, вы можете обратиться к подключам в цикле. Например:
- name: add several users
user:
name: "{{ item.name }}"
state: present
groups: "{{ item.groups }}"
loop:
- { name: 'testuser1', groups: 'wheel' }
- { name: 'testuser2', groups: 'root' }
При объединении условных операторов с циклом, утверждение when: обрабатывается отдельно для каждого элемента. См. Утверждение When для примеров.
Итерация по словарю
Для итерации по словарю используйте фильтр dict2items Dict:
- name: create a tag dictionary of non-empty tags
set_fact:
tags_dict: "{{ (tags_dict|default({}))|combine({item.key: item.value}) }}"
loop: "{{ tags|dict2items }}"
vars:
tags:
Environment: dev
Application: payment
Another: "{{ doesnotexist|default() }}"
when: item.value != ""
Здесь мы не хотим устанавливать пустые теги, поэтому создаем словарь, содержащий только непустые теги.
Регистрация переменных с циклом
Вы можете зарегистрировать вывод цикла в качестве переменной. Например:
- 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
fail:
msg: "The command ({{ item.cmd }}) did not have a 0 return code"
when: item.rc != 0
loop: "{{ echo.results }}"
Во время итерации результат текущего элемента будет помещен в переменную:
- shell: echo "{{ item }}"
loop:
- one
- two
register: echo
changed_when: echo.stdout != "one"
Сложные циклы
Итерация по вложенным спискам
Вы можете использовать выражения Jinja2 для итерации по сложным спискам. Например, цикл может объединить вложенные списки:
- name: give users access to multiple databases
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 для повторного выполнения задачи до тех пор, пока не будет выполнено определенное условие. Вот пример:
- shell: /usr/bin/foo
register: result
until: result.stdout.find("all systems go") != -1
retries: 5
delay: 10
Эта задача выполняется до 5 раз с задержкой в 10 секунд между каждой попыткой. Если результат любой попытки содержит «все системы работают» в stdout, задача завершается успешно. Значение по умолчанию для «повторов» — 3, а «задержка» — 5.
Чтобы увидеть результаты отдельных повторов, выполните плейлист с -vv.
Когда вы выполняете задачу с until и регистрируете результат в качестве переменной, зарегистрированная переменная будет включать ключ «попытки», который записывает номер повторов для задачи.
Примечание
Вы должны установить параметр until , если хотите, чтобы задача повторялась. Если until не определен, значение параметра retries принудительно устанавливается в 1.
Итерация по инвентаризации
Чтобы выполнить итерацию по вашей инвентаризации или только по подмножеству, вы можете использовать стандартный цикл loop с переменными ansible_play_batch или groups:
# show all the hosts in the inventory
- debug:
msg: "{{ item }}"
loop: "{{ groups['all'] }}"
# show all the hosts in the current play
- debug:
msg: "{{ item }}"
loop: "{{ ansible_play_batch }}"
Также существует специальный плагин lookup inventory_hostnames, который можно использовать так:
# show all the hosts in the inventory
- debug:
msg: "{{ item }}"
loop: "{{ query('inventory_hostnames', 'all') }}"
# show all the hosts matching the pattern, ie all but the group www
- debug:
msg: "{{ item }}"
loop: "{{ query('inventory_hostnames', 'all:!www') }}"
Дополнительную информацию о шаблонах можно найти на странице Работа с шаблонами
Обеспечение ввода списка для loop: query против lookup
Ключевое слово loop требует списка в качестве входных данных, но ключевое слово lookup по умолчанию возвращает строку с запятыми, разделяющими значения. Ansible 2.5 представил новую функцию Jinja2 под названием Вызов плагинов lookup с параметром 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 }}.
Пауза внутри цикла
Новое в версии 2.2.
Чтобы контролировать время (в секундах) между выполнением каждого элемента в цикле задачи, используйте директиву pause с loop_control:
# main.yml
- name: create servers, pause 3s before creating next
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
debug:
msg: "{{ item }} with index {{ my_idx }}"
loop:
- apple
- banana
- pear
loop_control:
index_var: my_idx
Определение имен внутренних и внешних переменных с помощью 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
- debug:
msg: "outer item={{ outer_item }} inner item={{ item }}"
loop:
- a
- b
- c
Примечание
Если Ansible обнаружит, что текущий цикл использует переменную, которая уже определена, он выведет ошибку, чтобы выполнить задачу.
Расширенные переменные цикла
Новое в версии 2.8.
Начиная с Ansible 2.8, вы можете получить расширенную информацию о цикле, используя параметр extended для управления циклом. Этот параметр предоставит следующую информацию.
| Переменная | Описание |
ansible_loop.allitems | Список всех элементов в цикле |
ansible_loop.index | Текущая итерация цикла. (Индексировано с 1) |
ansible_loop.index0 | Текущая итерация цикла. (Индексировано с 0) |
ansible_loop.revindex | Количество итераций от конца цикла (индексировано с 1) |
ansible_loop.revindex0 | Количество итераций от конца цикла (индексировано с 0) |
ansible_loop.first |
True если первая итерация |
ansible_loop.last |
True если последняя итерация |
ansible_loop.length | Количество элементов в цикле |
ansible_loop.previtem | Элемент из предыдущей итерации цикла. Не определено во время первой итерации. |
ansible_loop.nextitem | Элемент из следующей итерации цикла. Не определено во время последней итерации. |
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
С выпуском Ansible 2.5 рекомендуемый способ выполнения циклов — использование нового ключевого слова loop вместо циклов типа with_X.
Во многих случаях синтаксис loop лучше выражается с помощью фильтров, а не более сложного использования query или lookup.
Следующие примеры покажут, как преобразовать многие распространенные циклы типа with_ в циклы loop и фильтры.
с_списком
with_list непосредственно заменяется на loop.
- name: with_list
debug:
msg: "{{ item }}"
with_list:
- one
- two
- name: with_list -> loop
debug:
msg: "{{ item }}"
loop:
- one
- two
с_элементами
with_items заменяется на loop и фильтр flatten.
- name: with_items
debug:
msg: "{{ item }}"
with_items: "{{ items }}"
- name: with_items -> loop
debug:
msg: "{{ item }}"
loop: "{{ items|flatten(levels=1) }}"
с_индексированными_элементами
with_indexed_items заменяется на loop, фильтр flatten и loop_control.index_var.
- name: with_indexed_items
debug:
msg: "{{ item.0 }} - {{ item.1 }}"
with_indexed_items: "{{ items }}"
- name: with_indexed_items -> loop
debug:
msg: "{{ index }} - {{ item }}"
loop: "{{ items|flatten(levels=1) }}"
loop_control:
index_var: index
с_сглаженным
with_flattened заменяется на loop и фильтр flatten.
- name: with_flattened
debug:
msg: "{{ item }}"
with_flattened: "{{ items }}"
- name: with_flattened -> loop
debug:
msg: "{{ item }}"
loop: "{{ items|flatten }}"
с_вместе
with_together заменяется на loop и фильтр zip.
- name: with_together
debug:
msg: "{{ item.0 }} - {{ item.1 }}"
with_together:
- "{{ list_one }}"
- "{{ list_two }}"
- name: with_together -> loop
debug:
msg: "{{ item.0 }} - {{ item.1 }}"
loop: "{{ list_one|zip(list_two)|list }}"
с_словарем
with_dict может быть заменено на loop и либо фильтр dictsort, либо dict2items.
- name: with_dict
debug:
msg: "{{ item.key }} - {{ item.value }}"
with_dict: "{{ dictionary }}"
- name: with_dict -> loop (option 1)
debug:
msg: "{{ item.key }} - {{ item.value }}"
loop: "{{ dictionary|dict2items }}"
- name: with_dict -> loop (option 2)
debug:
msg: "{{ item.0 }} - {{ item.1 }}"
loop: "{{ dictionary|dictsort }}"
с_последовательностью
with_sequence заменяется на loop и функцию range, и потенциально фильтр format.
- name: with_sequence
debug:
msg: "{{ item }}"
with_sequence: start=0 end=4 stride=2 format=testuser%02x
- name: with_sequence -> loop
debug:
msg: "{{ 'testuser%02x' | format(item) }}"
# range is exclusive of the end point
loop: "{{ range(0, 4 + 1, 2)|list }}"
с_подэлементами
with_subelements заменяется на loop и фильтр subelements.
- name: with_subelements
debug:
msg: "{{ item.0.name }} - {{ item.1 }}"
with_subelements:
- "{{ users }}"
- mysql.hosts
- name: with_subelements -> loop
debug:
msg: "{{ item.0.name }} - {{ item.1 }}"
loop: "{{ users|subelements('mysql.hosts') }}"
с_вложенными/с_декартовыми
with_nested и with_cartesian заменяются на цикл и фильтр product.
- name: with_nested
debug:
msg: "{{ item.0 }} - {{ item.1 }}"
with_nested:
- "{{ list_one }}"
- "{{ list_two }}"
- name: with_nested -> loop
debug:
msg: "{{ item.0 }} - {{ item.1 }}"
loop: "{{ list_one|product(list_two)|list }}"
с_случайным_выбором
with_random_choice заменяется на использование только фильтра random, без необходимости loop.
- name: with_random_choice
debug:
msg: "{{ item }}"
with_random_choice: "{{ my_list }}"
- name: with_random_choice -> loop (No loop is needed here)
debug:
msg: "{{ my_list|random }}"
tags: random
См. также
- О playbook
- Введение в playbook
- Роли
- Организация playbook с помощью ролей
- Рекомендации
- Рекомендации по использованию playbook
- Условные операторы
- Условные операторы в playbook
- Использование переменных
- Все о переменных
- Список рассылки пользователей
- У вас есть вопросы? Заходите на форум!
- irc.freenode.net
- #ansible IRC чат-канал
© 2012–2018 Michael DeHaan
© 2018–2019 Red Hat, Inc.
Licensed under the GNU General Public License version 3.
https://docs.ansible.com/ansible/2.8/user_guide/playbooks_loops.html