Условные операторы
- Оператор When
- Циклы и условные операторы
- Загрузка пользовательских фактов
- Применение ‘when’ к ролям, импортам и включениям
- Условные импорты
- Выбор файлов и шаблонов на основе переменных
- Регистрация переменных
- Часто используемые факты
Часто результат выполнения задачи зависит от значения переменной, факта (информация о удалённой системе) или результата предыдущей задачи. В некоторых случаях значения переменных могут зависеть от других переменных. Дополнительные группы могут быть созданы для управления хостами на основе соответствия хостов другим критериям. Эта тема описывает, как используются условные операторы в плейбуках.
Примечание
Существует множество способов управления потоком выполнения в Ansible. Дополнительные примеры поддерживаемых условных операторов можно найти здесь: http://jinja.pocoo.org/docs/dev/templates/#comparisons.
Оператор When
Иногда вам нужно пропустить определённый шаг на определённом хосте. Это может быть что-то простое, например, не устанавливать определённый пакет, если операционная система имеет определённую версию, или что-то вроде выполнения некоторых действий по очистке, если файловая система заполняется.
В 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 is failed
# In older versions of ansible use ``success``, now both are valid but succeeded uses the correct tense.
- command: /bin/something_else
when: result is succeeded
- command: /bin/still/something_else
when: result is skipped
Примечание
как success и succeeded работают (fail/failed, и т.д.).
Напоминание: чтобы увидеть, какие факты доступны на определённой системе, вы можете сделать следующее:
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.
Переменные, определённые в плейбуках или инвентаре, также могут быть использованы. Примером может быть выполнение задачи на основе булевого значения переменной:
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 с циклами (см. Циклы), имейте в виду, что оператор when обрабатывается отдельно для каждого элемента. Это сделано намеренно:
tasks:
- command: echo {{ item }}
loop: [ 0, 2, 4, 6, 8, 10 ]
when: item > 5
Если вам нужно пропустить всю задачу в зависимости от того, определена ли переменная цикла, используйте фильтр |default для предоставления пустого итератора:
- command: echo {{ item }}
loop: "{{ mylist|default([]) }}"
when: item > 5
Если вы используете словарь в цикле:
- command: echo {{ item.key }}
loop: "{{ query('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 это работало с включением задач, но не с включением плейбука. В версии 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 вместо этого, задачи выполняются как ожидалось, потому что условное выражение к ним не применяется.
Условные импорты
Примечание
Это продвинутая тема, которая используется редко.
Иногда вы захотите сделать определённые вещи по-разному в плейбуке, основываясь на определённых критериях. Пример — один плейбук, который работает на нескольких платформах и версиях ОС.
Например, имя пакета 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/RedHat.yml apache: httpd somethingelse: 42
Как это работает? Для операционных систем Red Hat («CentOS», например) Ansible пытается импортировать первый файл ‘vars/RedHat.yml’. Если этот файл отсутствует, Ansible пытается загрузить ‘vars/os_defaults.yml’. Если файлы в списке не найдены, генерируется ошибка.
В Debian Ansible сначала ищет ‘vars/Debian.yml’ вместо ‘vars/RedHat.yml’, прежде чем перейти к ‘vars/os_defaults.yml’.
Подход Ansible к конфигурации — разделение переменных и задач, предотвращение превращения плейбуков в произвольный код с вложенными условными операторами — приводит к более упорядоченным и проверяемым правилам конфигурации, так как точек принятия решений для отслеживания меньше.
Выбор файлов и шаблонов на основе переменных
Примечание
Это продвинутая тема, которая используется редко. Вы, вероятно, можете пропустить этот раздел.
Иногда конфигурационный файл, который вы хотите скопировать, или шаблон, который вы будете использовать, может зависеть от переменной. Следующий конструкт выбирает первый доступный файл, подходящий для переменных данного хоста, что часто гораздо чище, чем размещение большого количества условных операторов if в шаблоне.
Следующий пример показывает, как создать шаблон конфигурационного файла, который значительно отличается между, например, CentOS и Debian:
- name: template a file
template:
src: "{{ item }}"
dest: /etc/myapp/foo.conf
loop: "{{ query('first_found', { 'files': myfiles, 'paths': mypaths}) }}"
vars:
myfiles:
- "{{ansible_distribution}}.conf"
- default.conf
mypaths: ['search_location_one/somedir/', '/opt/other_location/somedir/']
Регистрация переменных
Часто в плейбуке может быть полезно сохранить результат определённой команды в переменной и получить к ней доступ позже. Использование модуля 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’. Регистрируемый результат может быть использован в цикле задачи, если он преобразован в список (или уже является списком), как показано ниже. «stdout_lines» также доступен в объекте, хотя вы также могли бы вызвать «home_dirs.stdout.split()», если хотите, и разделить по другим полям:
- name: registered variable usage as a loop 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
loop: "{{ home_dirs.stdout_lines }}"
# same as loop: "{{ 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 == ""
Часто используемые факты
Следующие факты часто используются в условных операторах — см. примеры выше.
ansible_distribution
Возможные значения:
Alpine Altlinux Amazon Archlinux ClearLinux Coreos Debian Gentoo Mandriva NA OpenWrt OracleLinux RedHat Slackware SMGL SUSE VMwareESX
ansible_distribution_major_version
Это будет главная версия операционной системы. Например, значение будет 16 для Ubuntu 16.04.
ansible_os_family
Возможные значения:
AIX Alpine Altlinux Archlinux Darwin Debian FreeBSD Gentoo HP-UX Mandrake RedHat SGML Slackware Solaris Suse
См. также
- Работа с 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.6/user_guide/playbooks_conditionals.html