Условные операторы
- Условное выполнение (When Statement)
- Циклы и условные операторы
- Загрузка пользовательских фактов
- Применение `when` к ролям, импортам и включениям
- Условные импорты
- Выбор файлов и шаблонов на основе переменных
- Регистрация переменных
Часто результат выполнения задачи зависит от значения переменной, факта (информация о удалённой системе) или результата предыдущей задачи. В некоторых случаях значения переменных могут зависеть от других переменных. Кроме того, можно создавать дополнительные группы для управления хостами на основе соответствия хостов определённым критериям. Ansible предоставляет множество возможностей для управления потоком выполнения. Дополнительные примеры поддерживаемых условных операторов можно найти здесь: http://jinja.pocoo.org/docs/dev/templates/#comparisons
Давайте углубимся в их использование.
Условное выполнение (When Statement)
Иногда вам нужно пропустить определённый шаг на конкретном хосте. Это может быть что-то простое, например, не установка определённого пакета, если операционная система имеет определённую версию, или что-то более сложное, например, выполнение шагов очистки, если файловая система переполняется.
В Ansible это легко сделать с помощью клаузы `when`, которая содержит выражение Jinja2 в сыром виде без двойных фигурных скобок (см. Переменные). На самом деле это довольно просто:
tasks:
- name: "shut down Debian flavored systems"
command: /sbin/shutdown -t now
when: ansible_os_family == "Debian"
# note that Ansible facts and vars like ansible_os_family can be used
# directly in conditionals without double curly braces
Вы также можете использовать скобки для группировки условий:
tasks:
- name: "shut down CentOS 6 and Debian 7 systems"
command: /sbin/shutdown -t now
when: (ansible_distribution == "CentOS" and ansible_distribution_major_version == "6") or
(ansible_distribution == "Debian" and ansible_distribution_major_version == "7")
Несколько условий, которые должны быть истинными одновременно (логическое «и»), также могут быть указаны в виде списка:
tasks:
- name: "shut down CentOS 6 systems"
command: /sbin/shutdown -t now
when:
- ansible_distribution == "CentOS"
- ansible_distribution_major_version == "6"
В операторах `when` можно использовать множество «фильтров» Jinja2, некоторые из которых уникальны и предоставляются Ansible. Предположим, мы хотим проигнорировать ошибку одного оператора и затем условно выполнить что-то в зависимости от успеха или неудачи:
tasks:
- command: /bin/false
register: result
ignore_errors: True
- command: /bin/something
when: result|failed
# In older versions of ansible use |success, now both are valid but succeeded uses the correct tense.
- command: /bin/something_else
when: result|succeeded
- command: /bin/still/something_else
when: result|skipped
Примечание
фильтры были обновлены в версии 2.1, поэтому оба `success` и `succeeded` работают (`fail`/`failed, и т. д.).
Обратите внимание, что это лёгкое предвосхищение оператора «register». Мы вернёмся к нему немного позже в этой главе.
Напоминание: чтобы увидеть какие факты доступны на конкретной системе, вы можете сделать следующее:
ansible hostname.example.com -m setup
Подсказка: Иногда вы получите переменную, которая является строкой, и захотите выполнить математическое сравнение с ней. Вы можете сделать это так:
tasks:
- shell: echo "only on Red Hat 6, derivatives, and later"
when: ansible_os_family == "RedHat" and ansible_lsb.major_release|int >= 6
Примечание
в приведённом выше примере требуется пакет `lsb_release` на целевом хосте, чтобы вернуть факт `ansible_lsb.major_release`.
Переменные, определённые в playbook или инвентаризации, также могут быть использованы. Примером может служить выполнение задачи на основе булевого значения переменной:
vars: epic: true
Затем условное выполнение может выглядеть так:
tasks:
- shell: echo "This certainly is epic!"
when: epic
или:
tasks:
- shell: echo "This certainly isn't epic!"
when: not epic
Если необходимая переменная не задана, вы можете пропустить или отклонить выполнение с помощью теста Jinja2 `defined`. Например:
tasks:
- shell: echo "I've got '{{ foo }}' and am not afraid to use it!"
when: foo is defined
- fail: msg="Bailing out. this play requires 'bar'"
when: bar is undefined
Это особенно полезно в сочетании с условным импортом файлов vars (см. ниже). Как показывают примеры, вам не нужно использовать `{{ }}` для использования переменных в условных операторах, так как они уже подразумеваются.
Циклы и условные операторы
Объединяя `when` с `with_items` (см. Циклы), имейте в виду, что оператор `when` обрабатывается отдельно для каждого элемента. Это сделано по умолчанию:
tasks:
- command: echo {{ item }}
with_items: [ 0, 2, 4, 6, 8, 10 ]
when: item > 5
Если вам нужно пропустить всю задачу в зависимости от того, определена ли переменная цикла, используйте фильтр `|default`, чтобы предоставить пустой итератор:
- command: echo {{ item }}
with_items: "{{ mylist|default([]) }}"
when: item > 5
Если вы используете `with_dict`, который не принимает список:
- command: echo {{ item.key }}
with_dict: "{{ mydict|default({}) }}"
when: item.value > 5
Загрузка пользовательских фактов
Также легко предоставлять свои собственные факты, если вам это нужно, что описано в Разработке модулей. Для их запуска просто сделайте вызов своего собственного модуля сбора пользовательских фактов в верхней части списка задач, и переменные, возвращаемые там, будут доступны для будущих задач:
tasks:
- name: gather site specific fact data
action: site_facts
- command: /usr/bin/thingy
when: my_custom_fact_just_retrieved_from_the_remote_system == '1234'
Применение `when` к ролям, импортам и включениям
Обратите внимание, что если у вас есть несколько задач, которые все используют одно и то же условное выражение, вы можете прикрепить условное выражение к оператору включения задачи, как показано ниже. Все задачи оцениваются, но условное выражение применяется к каждой задаче:
- import_tasks: tasks/sometasks.yml when: "'reticulating splines' in output"
Примечание
В версиях до 2.0 это работало с включениями задач, но не с включениями playbook. В версии 2.0 это работает с обоими.
Или с ролью:
- hosts: webservers
roles:
- { role: debian_stock_config, when: ansible_os_family == 'Debian' }
Вы заметите, что по умолчанию Ansible выводит много «пропущенных» результатов при использовании этого подхода на системах, которые не соответствуют критериям. Подробнее о модуле `group_by` в документации О модулях для более эффективного достижения той же цели.
При использовании с `include_*` задачами вместо импортов условное выражение применяется только к самой задаче включения, а не к другим задачам в включённых файлах. Общая ситуация, где это различие важно, приведена ниже:
# include a file to define a variable when it is not already defined
# main.yml
- include_tasks: other_tasks.yml
when: x is not defined
# other_tasks.yml
- set_fact:
x: foo
- debug:
var: x
В приведенном выше примере, если `import_tasks` было использовано вместо этого, обе включенные задачи также были бы пропущены. С `include_tasks` вместо этого задачи выполняются как ожидается, потому что условное выражение к ним не применяется.
Условные импорты
Примечание
Это продвинутая тема, которая редко используется. Вы, вероятно, можете пропустить этот раздел.
Иногда в playbook вы захотите выполнять различные действия в зависимости от определённых критериев. Хорошим примером является playbook, который работает на нескольких платформах и версиях ОС.
Например, имя пакета Apache может отличаться между CentOS и Debian, но это легко обрабатывается с минимальным синтаксисом в Ansible Playbook:
---
- hosts: all
remote_user: root
vars_files:
- "vars/common.yml"
- [ "vars/{{ ansible_os_family }}.yml", "vars/os_defaults.yml" ]
tasks:
- name: make sure apache is started
service: name={{ apache }} state=started
Примечание
Переменная `ansible_os_family` интерполируется в список имён файлов, определяемых для vars_files.
Напоминание: различные файлы YAML содержат только ключи и значения:
--- # for vars/CentOS.yml apache: httpd somethingelse: 42
Как это работает? Если операционная система была «CentOS», Ansible первым попытался бы импортировать файл «vars/CentOS.yml», а затем «/vars/os_defaults.yml», если этот файл не существовал. Если файлы в списке не были найдены, было бы выведено сообщение об ошибке. На Debian Ansible первым искал бы «vars/Debian.yml», а затем «vars/os_defaults.yml». Довольно просто.
Для использования этой функции условного импорта вам нужно установить facter или ohai перед запуском playbook, но, конечно, вы можете сделать это с помощью Ansible, если захотите:
# for facter ansible -m yum -a "pkg=facter state=present" ansible -m yum -a "pkg=ruby-json state=present" # for ohai ansible -m yum -a "pkg=ohai state=present"
Подход Ansible к конфигурации — разделение переменных от задач, не позволяет превращать ваши playbooks в произвольный код с уродливыми вложенными if, условными операторами и т. д. — и приводит к более структурированным и проверяемым правилам конфигурации — особенно потому, что точек принятия решений для отслеживания минимальное количество.
Выбор файлов и шаблонов на основе переменных
Примечание
Это продвинутая тема, которая редко используется. Вы, вероятно, можете пропустить этот раздел.
Иногда конфигурационный файл, который вы хотите скопировать, или шаблон, который вы будете использовать, может зависеть от переменной. Следующая конструкция выбирает первый доступный файл, подходящий для переменных данного хоста, что часто намного чище, чем вставлять множество условных операторов в шаблон.
Следующий пример показывает, как создать шаблон конфигурационного файла, который сильно отличается между, скажем, CentOS и Debian:
- name: template a file
template: src={{ item }} dest=/etc/myapp/foo.conf
with_first_found:
- files:
- {{ ansible_distribution }}.conf
- default.conf
paths:
- search_location_one/somedir/
- /opt/other_location/somedir/
Регистрация переменных
Часто в playbook может быть полезно сохранить результат определённой команды в переменной и обратиться к ней позже. Использование модуля `command` таким образом может в значительной степени устранить необходимость написания фактов, специфичных для сайта, например, вы можете проверить наличие конкретной программы.
Ключевое слово «register» определяет, в какую переменную сохранить результат. Результирующие переменные могут быть использованы в шаблонах, операторах действий или операторах `when`. Вот как это выглядит (в очевидно тривиальном примере):
- name: test play
hosts: all
tasks:
- shell: cat /etc/motd
register: motd_contents
- shell: echo "motd contains the word hi"
when: motd_contents.stdout.find('hi') != -1
Как показано ранее, содержимое зарегистрированной переменной доступно со значением `stdout`. Регистрируемый результат может быть использован в «with_items» задачи, если он преобразован в список (или уже является списком), как показано ниже. «stdout_lines» также доступен в объекте, хотя вы также можете вызвать `home_dirs.stdout.split()`, если хотите, и можете разделить по другим полям:
- name: registered variable usage as a with_items list
hosts: all
tasks:
- name: retrieve the list of home directories
command: ls /home
register: home_dirs
- name: add home dirs to the backup spooler
file: path=/mnt/bkspool/{{ item }} src=/home/{{ item }} state=link
with_items: "{{ home_dirs.stdout_lines }}"
# same as with_items: "{{ home_dirs.stdout.split() }}"
Как показано ранее, содержимое зарегистрированной переменной доступно со значением `stdout`. Вы можете проверить содержимое зарегистрированной переменной на пустоту:
- name: check registered variable for emptiness
hosts: all
tasks:
- name: list contents of directory
command: ls mydir
register: contents
- name: check contents for emptiness
debug: msg="Directory is empty"
when: contents.stdout == ""
См. также
- Playbooks
- Введение в playbooks
- Роли
- Организация playbook по ролям
- Рекомендации по разработке
- Рекомендации по разработке 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.4/playbooks_conditionals.html