Spec-Zone.ru › Ansible 2.10

Циклы

Иногда требуется выполнить задачу несколько раз. В программировании это называется циклом. Типичные циклы в Ansible включают изменение владельца нескольких файлов и/или каталогов с помощью модуля file, создание нескольких пользователей с помощью модуля user и повторение шага опроса до достижения определённого результата. Ansible предлагает два ключевых слова для создания циклов: loop и with_<lookup>.

Примечание

  • Мы добавили loop в Ansible 2.5. Это ещё не полная замена для with_<lookup>, но мы рекомендуем его для большинства случаев использования.
  • Мы не устарели with_<lookup> - этот синтаксис останется действительным в обозримом будущем.
  • Мы работаем над улучшением синтаксиса loop - следите за этой страницей и журналом изменений для получения обновлений.
  • Сравнение loop и with_*
  • Стандартные циклы

    • Итерирование по простому списку
    • Итерирование по списку словарей
    • Итерирование по словарю
  • Регистрация переменных с циклом
  • Сложные циклы

    • Итерирование по вложенным спискам
    • Повторное выполнение задачи до достижения условия
    • Итерирование по инвентаризации
  • Обеспечение входных данных в виде списка для цикла: использование query вместо lookup
  • Добавление управления циклами

    • Ограничение вывода цикла с меткой
    • Пауза внутри цикла
    • Отслеживание прогресса в цикле с переменной индекса
    • Определение имён внутренних и внешних переменных с loop_var
    • Расширенные переменные цикла
    • Получение имени вашей переменной цикла
  • Миграция с with_X на loop

    • with_list
    • with_items
    • with_indexed_items
    • with_flattened
    • with_together
    • with_dict
    • with_sequence
    • with_subelements
    • with_nested/with_cartesian
    • with_random_choice

Сравнение 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

Вы можете определить список в файле переменных или в разделе «переменные» своего плейбука, а затем сослаться на имя списка в задаче:

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: обрабатывается отдельно для каждого элемента. См. Базовые условные операторы с 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 имеет нулевую индексацию.

Определение имен внутренних и внешних переменных с 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 для управления циклом. Этот параметр предоставит следующую информацию.

Переменная

Описание

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

В большинстве случаев циклы работают лучше с ключевым словом 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 заменяются на loop и фильтр 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 с помощью ролей

Советы и хитрости

Советы и хитрости для playbook

Условные операторы

Условные операторы в playbook

Использование переменных

Все о переменных

Список рассылки пользователей

Есть вопрос? Загляните в группу Google!

irc.freenode.net

IRC-чат-канал #ansible

© 2012–2018 Michael DeHaan
© 2018–2019 Red Hat, Inc.
Licensed under the GNU General Public License version 3.
https://docs.ansible.com/ansible/2.10/user_guide/playbooks_loops.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API