Spec-Zone.ru › Ansible 2.9

Циклы

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

Примечание

  • Мы добавили loop в Ansible 2.5. Это пока не полная замена для with_<lookup>, но мы рекомендуем его для большинства случаев.
  • Мы не устарели использование with_<lookup> — этот синтаксис останется допустимым в обозримом будущем.
  • Мы планируем улучшить loop синтаксис — следите за этой страницей и changelog для обновлений.
  • Сравнение loop и with_*
  • Стандартные циклы
    • Итерация по простому списку
    • Итерация по списку словарей
    • Итерация по словарю
  • Регистрация переменных с циклом
  • Сложные циклы
    • Итерация по вложенным спискам
    • Повтор задачи до выполнения условия
    • Итерация по инвентаризации
  • Обеспечение входных данных в виде списка для цикла: loop: query против lookup
  • Добавление управления циклами
    • Ограничение вывода цикла с меткой label
    • Пауза внутри цикла
    • Отслеживание прогресса цикла с помощью переменной индекса index_var
    • Определение имён внутренних и внешних переменных с помощью loop_var
    • Расширенные переменные цикла
    • Доступ к имени вашей переменной цикла
  • Миграция из 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

Сравнение loop и with_*

  • Ключевые слова with_<lookup> полагаются на Плагины Lookup — даже items является плагином lookup.
  • Ключевое слово loop эквивалентно with_list, и является лучшим выбором для простых циклов.
  • Ключевое слово loop не примет строку в качестве входных данных, см. Обеспечение входных данных в виде списка для цикла: query vs. lookup.
  • В целом, любое использование with_*, описанное в Миграция из with_X в цикл, может быть обновлено для использования 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

Вы можете определить список в файле переменных или в разделе '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"

Вы можете передать список напрямую параметру некоторых плагинов. Большинство модулей упаковки, такие как 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 Фильтр словаря:

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

Дополнительную информацию о шаблонах можно найти на странице Шаблоны: нацеливание на хосты и группы

Обеспечение входных данных в виде списка для цикла: query против lookup

Ключевое слово loop требует списка в качестве входных данных, но ключевое слово lookup по умолчанию возвращает строку с запятыми, разделяющими значения. Ansible 2.5 представил новую функцию Jinja2 с именем Вызов плагинов lookup с запросом, которая всегда возвращает список, предлагая более простой интерфейс и более предсказуемый вывод от плагинов 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
  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

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

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
Использование переменных
Все о переменных
Список рассылки пользователей
У вас есть вопросы? Зайдите на эту группу Google!
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.9/user_guide/playbooks_loops.html

Spec-Zone.ru

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