Spec-Zone.ru › Ansible 2.7

Циклы

Часто вам потребуется выполнить много действий в одной задаче, например, создать много пользователей, установить много пакетов или повторить шаг опроса до достижения определенного результата.

В этой главе рассказывается о том, как использовать циклы в плейбуках.

  • Стандартные циклы
  • Сложные циклы
  • Использование lookup и query с циклом
  • Циклы «до тех пор, пока»
  • Использование register с циклом
  • Перебор инвентаризации
  • Управление циклом
  • Миграция с with_X на цикл
    • with_list
    • with_items
    • with_indexed_items
    • with_flattened
    • with_together
    • with_dict
    • with_sequence
    • with_subelements
    • with_nested/with_cartesian
    • with_random_choice

Стандартные циклы

Для экономии набора текста повторяющиеся задачи могут быть записаны в сокращенной форме следующим образом:

- name: add several users
  user:
    name: "{{ item }}"
    state: present
    groups: "wheel"
  loop:
     - testuser1
     - testuser2

Если вы определили список YAML в файле переменных или в разделе «vars», вы также можете сделать следующее:

loop: "{{ somelist }}"

Вышеприведенное эквивалентно:

- name: add user testuser1
  user:
    name: "testuser1"
    state: present
    groups: "wheel"
- name: add user testuser2
  user:
    name: "testuser2"
    state: present
    groups: "wheel"

Примечание

До версии 2.5 Ansible в основном использовал ключевые слова with_<lookup> для создания циклов, ключевое слово loop в основном аналогично ключевому слову with_list.

Некоторые плагины, такие как модули yum и apt, могут напрямую принимать списки в свои параметры. Это более эффективно, чем перебор задач. Обратитесь к документации каждого действия для получения подробностей. Вот пример:

- name: optimal yum
  yum:
    name: "{{list_of_packages}}"
    state: present

- name: non optimal yum, not only slower but might 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 Фильтр словаря:

- 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 != ""

Здесь мы не хотим устанавливать пустые теги, поэтому мы создаём словарь, содержащий только непустые теги.

Сложные циклы

Иногда вам требуется больше, чем предоставляет простой список. Вы можете использовать выражения Jinja2 для создания сложных списков. Например, используя поиск «nested», вы можете объединить списки:

- 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 }}"

Примечание

with_ циклы фактически являются комбинацией with_ + lookup(), даже items является поиском. loop можно использовать аналогичным образом, как показано выше.

Использование lookup и query с циклом

В Ansible 2.5 была введена новая функция Jinja2 под названием query, которая предлагает несколько преимуществ по сравнению с lookup при использовании нового ключевого слова loop.

Это лучше описано в документации по lookup. Однако, query предоставляет более простой интерфейс и более предсказуемый результат работы плагинов lookup, обеспечивая лучшую совместимость с loop.

В определённых ситуациях функция lookup может не вернуть список, который loop требует.

Следующие вызовы эквивалентны, используя wantlist=True с lookup для обеспечения возвращаемого типа списка:

loop: "{{ query('inventory_hostnames', 'all') }}"

loop: "{{ lookup('inventory_hostnames', 'all', wantlist=True) }}"

Циклы «до тех пор, пока»

Новое в версии 1.4.

Иногда вам нужно будет повторить задачу до тех пор, пока не будет выполнено определенное условие. Вот пример:

- shell: /usr/bin/foo
  register: result
  until: result.stdout.find("all systems go") != -1
  retries: 5
  delay: 10

В приведенном выше примере модуль shell запускается рекурсивно до тех пор, пока в стандартном выводе модуля не будет строка «все системы работают», или задача не будет перепробована 5 раз с задержкой в 10 секунд. Значение по умолчанию для «retries» равно 3, а «delay» равно 5.

Задача возвращает результаты, полученные последней выполненной задачей. Результаты отдельных попыток можно увидеть с помощью опции -vv. Регистрируемая переменная также будет иметь новый ключ «attempts», который будет содержать количество попыток выполнения задачи.

Примечание

Если параметр until не определен, значение параметра retries принудительно устанавливается в 1.

Использование register с циклом

После использования register с циклом структура данных, помещённая в переменную, будет содержать атрибут results, который представляет собой список всех ответов от модуля.

Вот пример использования register с loop:

- shell: "echo {{ item }}"
  loop:
    - "one"
    - "two"
  register: echo

Это отличается от структуры данных, возвращаемой при использовании 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"

Перебор инвентаризации

Если вы хотите перебрать инвентаризацию или только её подмножество, есть несколько способов. Можно использовать обычный 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') }}"

Дополнительную информацию о шаблонах можно найти на странице Работа с шаблонами

Управление циклом

Новое в версии 2.1.

В версии 2.0 вы снова можете использовать циклы и включения задач (но не плейбуков). Это добавляет возможность перебирать набор задач в едином цикле. Ansible по умолчанию устанавливает переменную цикла item для каждого цикла, что приводит к перезаписи значения item из «внешних» циклов в этих вложенных циклах. Начиная с Ansible 2.1, опция 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.2.

При использовании сложных структур данных для циклов вывод может стать слишком «загромождённым». В этом случае поможет директива label:

- 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 }}"

Теперь будет отображаться только поле label вместо всей структуры на каждый item, по умолчанию используется {{ item }} для отображения элементов обычным способом.

Новое в версии 2.2.

Другой вариант управления циклом — pause, который позволяет управлять временем (в секундах) между выполнением элементов в цикле задачи.

# main.yml
- name: create servers, pause 3s before creating next
  digital_ocean:
    name: "{{ item }}"
    state: present
  loop:
    - server1
    - server2
  loop_control:
    pause: 3

Новое в версии 2.5.

Если вам нужно отслеживать, где вы находитесь в цикле, вы можете использовать опцию index_var для управления циклом, чтобы указать имя переменной, которая будет содержать текущий индекс цикла:

- name: count our fruit
  debug:
    msg: "{{ item }} with index {{ my_idx }}"
  loop:
    - apple
    - banana
    - pear
  loop_control:
    index_var: my_idx

Миграция с with_X на цикл

С выпуском Ansible 2.5 рекомендуемым способом выполнения циклов является использование нового ключевого слова loop вместо циклов стиля with_X.

Во многих случаях синтаксис loop лучше выражается с помощью фильтров, а не с более сложным использованием query или lookup.

Следующие примеры покажут, как преобразовать многие распространённые циклы стиля with_ в циклы loop и фильтры.

with_list

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

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

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

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

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

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

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

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

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
Канал чата 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.7/user_guide/playbooks_loops.html

Spec-Zone.ru

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