Условные выражения
- Оператор When
- Циклы и условные выражения
- Загрузка пользовательских фактов
- Применение ‘when’ к ролям, импортам и включениям
- Условные импорты
- Выбор файлов и шаблонов на основе переменных
- Регистрация переменных
- Часто используемые факты
Часто результат выполнения книги задач зависит от значения переменной, факта (информация о удалённой системе) или результата предыдущей задачи. В некоторых случаях значения переменных могут зависеть от других переменных. Можно создавать дополнительные группы для управления хостами на основе соответствия хостов другим критериям. Эта тема описывает использование условных выражений в книгах задач.
Примечание
Существует множество способов управления потоком выполнения в Ansible. Более подробные примеры поддерживаемых условных выражений можно найти здесь: http://jinja.pocoo.org/docs/dev/templates/#comparisons.
Оператор When
Иногда вам нужно пропустить определённый шаг на конкретном хосте. Это может быть что-то простое, например, не устанавливать определённый пакет, если операционная система имеет определённую версию, или что-то вроде выполнения шагов очистки, если файловая система переполняется.
В Ansible это легко сделать с помощью оператора when, содержащего выражение Jinja2 в сыром виде без двойных фигурных скобок (см. group_by – Создание групп Ansible на основе фактов). На самом деле это довольно просто:
tasks:
- name: "shut down Debian flavored systems"
command: /sbin/shutdown -t now
when: ansible_facts['os_family'] == "Debian"
# note that all variables 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_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "6") or
(ansible_facts['distribution'] == "Debian" and ansible_facts['distribution_major_version'] == "7")
Несколько условий, которые все должны быть истинными (логическое «и»), также могут быть указаны как список:
tasks:
- name: "shut down CentOS 6 systems"
command: /sbin/shutdown -t now
when:
- ansible_facts['distribution'] == "CentOS"
- ansible_facts['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, и т.д.).
Чтобы увидеть доступные факты на конкретной системе, вы можете сделать следующее в книге задач:
- debug: var=ansible_facts
Совет: Иногда вы получаете переменную, которая является строкой, и хотите выполнить математическое сравнение. Вы можете сделать это следующим образом:
tasks:
- shell: echo "only on Red Hat 6, derivatives, and later"
when: ansible_facts['os_family'] == "RedHat" and ansible_facts['lsb']['major_release']|int >= 6
Примечание
в приведенном выше примере требуется пакет lsb_release на целевом хосте для возврата факта «lsb major_release».
Переменные, определённые в книгах задач или инвентаре, также могут быть использованы, просто убедитесь, что применили фильтр |bool к переменным, не являющимся булевыми (например, строковым переменным со значениями «да», «вкл», «1», «истина»). Пример может быть в выполнении задачи на основе булевого значения переменной:
vars: epic: true monumental: "yes"
Тогда условное выполнение может выглядеть так:
tasks:
- shell: echo "This certainly is epic!"
when: epic or monumental|bool
или:
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_facts['os_family'] == 'Debian'
Вы заметите много вывода «пропущено» по умолчанию в Ansible при использовании этого подхода на системах, которые не соответствуют критериям. Во многих случаях модуль group_by может быть более эффективным способом достижения той же цели; см. Различия в операционных системах и дистрибутивах.
При использовании условного выражения с include_* задачами вместо импорта, оно применяется only к задаче включения, а не к другим задачам в включаемом файле(ах). Распространённая ситуация, где это различие важно, следующая:
# We wish to include a file to define a variable when it is not
# already defined
# main.yml
- import_tasks: other_tasks.yml # note "import"
when: x is not defined
# other_tasks.yml
- set_fact:
x: foo
- debug:
var: x
Это расширяется во время включения до эквивалента:
- set_fact:
x: foo
when: x is not defined
- debug:
var: x
when: x is not defined
Таким образом, если x изначально не определено, задача debug будет пропущена. Используя include_tasks вместо import_tasks, обе задачи из other_tasks.yml будут выполнены как ожидается.
Дополнительную информацию о различиях между include и import см. в Создание многоразовых книг задач.
Условные импорты
Примечание
Это расширенная тема, которая используется редко.
Иногда вам нужно делать определённые вещи по-разному в книге задач в зависимости от определённых критериев. Хороший пример - одна книга задач, работающая на нескольких платформах и версиях ОС.
В качестве примера имя пакета Apache может отличаться между CentOS и Debian, но это легко обрабатывается минимальным синтаксисом в книге задач Ansible:
---
- hosts: all
remote_user: root
vars_files:
- "vars/common.yml"
- [ "vars/{{ ansible_facts['os_family'] }}.yml", "vars/os_defaults.yml" ]
tasks:
- name: make sure apache is started
service: name={{ apache }} state=started
Примечание
Переменная “ansible_facts[‘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 к конфигурации - разделение переменных от задач, предотвращение превращения книг задач в произвольный код с вложенными условными выражениями - приводит к более структурированным и проверяемым правилам конфигурации, потому что точек принятия решений для отслеживания меньше.
Выбор файлов и шаблонов на основе переменных
Примечание
Это расширенная тема, которая используется редко. Можно пропустить этот раздел.
Иногда конфигурационный файл, который вы хотите скопировать, или шаблон, который вы будете использовать, может зависеть от переменной. Следующее выражение выбирает первый доступный файл, подходящий для переменных данного хоста, что часто гораздо чище, чем вставка большого количества условных выражений в шаблон.
Следующий пример демонстрирует, как создать шаблон конфигурационного файла, который сильно отличается между, скажем, 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_facts['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_facts[‘distribution’]
Возможные значения (пример, неполный список):
Alpine Altlinux Amazon Archlinux ClearLinux Coreos CentOS Debian Fedora Gentoo Mandriva NA OpenWrt OracleLinux RedHat Slackware SMGL SUSE Ubuntu VMwareESX
ansible_facts[‘distribution_major_version’]
Это будет основная версия операционной системы. Например, значение будет 16 для Ubuntu 16.04.
ansible_facts[‘os_family’]
Возможные значения (пример, неполный список):
AIX Alpine Altlinux Archlinux Darwin Debian FreeBSD Gentoo HP-UX Mandrake RedHat SGML Slackware Solaris Suse Windows
См. также
- Работа с 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_conditionals.html