Spec-Zone.ru › Ansible 2.6

Циклы

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

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

  • Стандартные циклы
  • Сложные циклы
  • Использование 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 запускается рекурсивно до тех пор, пока результат модуля не содержит «все системы работают» в выводе stdout, или задача не была повторена 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
Введение в playbooks
Роли
Организация Playbook по ролям
Рекомендации
Рекомендации по использованию playbooks
Условные операторы
Условные операторы в playbooks
Переменные
Все о переменных
Список рассылки пользователей
Есть вопрос? Заходите в группу 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.6/user_guide/playbooks_loops.html

Spec-Zone.ru

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