Условные операторы
В плейбуке вы можете выполнить разные задачи или установить разные цели в зависимости от значения факта (данные о удаленной системе), переменной или результата предыдущей задачи. Вы можете задать значение некоторых переменных, зависящих от значения других переменных. Или вы можете создать дополнительные группы хостов, основанные на том, соответствуют ли хосты другим критериям. Все это можно сделать с помощью условных операторов.
Ansible использует тесты и фильтры Jinja2 в условных операторах. Ansible поддерживает все стандартные тесты и фильтры, а также добавляет некоторые уникальные.
Примечание
Существует много способов управлять потоком выполнения в Ansible. Дополнительные примеры поддерживаемых условных операторов можно найти по адресу https://jinja.palletsprojects.com/en/master/templates/#comparisons.
Основные условные операторы с when
Самый простой условный оператор применяется к одной задаче. Создайте задачу, а затем добавьте оператор when, который применяет тест. Оператор when представляет собой исходное выражение Jinja2 без двойных фигурных скобок (см. group_by – Создание групп Ansible на основе фактов). При выполнении задачи или плейбука Ansible оценивает тест для всех хостов. На любом хосте, где тест проходит (возвращает значение True), Ansible выполняет эту задачу. Например, если вы устанавливаете mysql на несколько машин, некоторые из которых имеют включенный SELinux, у вас может быть задача по настройке SELinux для разрешения запуска mysql. Вы хотите, чтобы эта задача выполнялась только на машинах, на которых включен SELinux:
tasks:
- name: Configure SELinux to start mysql on any port
ansible.posix.seboolean:
name: mysql_connect_any
state: true
persistent: yes
when: ansible_selinux.status == "enabled"
# all variables can be used directly in conditionals without double curly braces
Условные операторы на основе ansible_facts
Часто вы хотите выполнить или пропустить задачу на основе фактов. Факты — это атрибуты отдельных хостов, включая IP-адрес, операционную систему, состояние файловой системы и многое другое. С помощью условных операторов на основе фактов:
- Вы можете установить определенный пакет только в том случае, если операционная система является определенной версией.
- Вы можете пропустить настройку брандмауэра на хостах с внутренними IP-адресами.
- Вы можете выполнить задачи очистки только при заполнении файловой системы.
См. Часто используемые факты для списка фактов, которые часто встречаются в условных операторах. Не все факты существуют для всех хостов. Например, факт ‘lsb_major_release’, используемый в примере ниже, существует только тогда, когда пакет lsb_release установлен на целевом хосте. Чтобы увидеть доступные факты на своих системах, добавьте задачу debug в свой плейбук:
- name: Show facts available on the system
ansible.builtin.debug:
var: ansible_facts
Вот пример условного оператора на основе факта:
tasks:
- name: Shut down Debian flavored systems
ansible.builtin.command: /sbin/shutdown -t now
when: ansible_facts['os_family'] == "Debian"
Если у вас несколько условий, вы можете сгруппировать их в скобки:
tasks:
- name: Shut down CentOS 6 and Debian 7 systems
ansible.builtin.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")
Вы можете использовать логические операторы для объединения условий. Когда у вас есть несколько условий, которые должны быть истинными (то есть логический and), вы можете указать их как список:
tasks:
- name: Shut down CentOS 6 systems
ansible.builtin.command: /sbin/shutdown -t now
when:
- ansible_facts['distribution'] == "CentOS"
- ansible_facts['distribution_major_version'] == "6"
Если факт или переменная являются строкой, и вам нужно выполнить математическое сравнение, используйте фильтр, чтобы убедиться, что Ansible считывает значение как целое число:
tasks:
- ansible.builtin.shell: echo "only on Red Hat 6, derivatives, and later"
when: ansible_facts['os_family'] == "RedHat" and ansible_facts['lsb']['major_release'] | int >= 6
Условные операторы на основе зарегистрированных переменных
Часто в плейбуке вы хотите выполнить или пропустить задачу в зависимости от результата предыдущей задачи. Например, вы можете настроить службу после её обновления в предыдущей задаче. Чтобы создать условный оператор на основе зарегистрированной переменной:
- Зарегистрируйте результат предыдущей задачи как переменную.
- Создайте условный тест на основе зарегистрированной переменной.
Вы создаете имя зарегистрированной переменной, используя ключевое слово register. Зарегистрированная переменная всегда содержит статус задачи, которая её создала, а также любой вывод, который она сгенерировала. Вы можете использовать зарегистрированные переменные в шаблонах и строках действий, а также в условных when операторах. Вы можете получить доступ к строковому содержимому зарегистрированной переменной, используя variable.stdout. Например:
- name: Test play
hosts: all
tasks:
- name: Register a variable
ansible.builtin.shell: cat /etc/motd
register: motd_contents
- name: Use the variable in conditional statement
ansible.builtin.shell: echo "motd contains the word hi"
when: motd_contents.stdout.find('hi') != -1
Вы можете использовать зарегистрированные результаты в цикле задачи, если переменная является списком. Если переменная не является списком, вы можете преобразовать её в список, либо с помощью stdout_lines, либо с помощью variable.stdout.split(). Вы также можете разделить строки по другим полям:
- name: Registered variable usage as a loop list
hosts: all
tasks:
- name: Retrieve the list of home directories
ansible.builtin.command: ls /home
register: home_dirs
- name: Add home dirs to the backup spooler
ansible.builtin.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
ansible.builtin.command: ls mydir
register: contents
- name: Check contents for emptiness
ansible.builtin.debug:
msg: "Directory is empty"
when: contents.stdout == ""
Ansible всегда регистрирует что-то в зарегистрированной переменной для каждого хоста, даже на хостах, где задача завершается неудачно или Ansible пропускает задачу, потому что условие не выполняется. Чтобы выполнить последующую задачу на этих хостах, запросите в зарегистрированной переменной значение is skipped (а не «undefined» или «default»). Дополнительную информацию см. в разделе Регистрация переменных. Вот примеры условных операторов, основанных на успехе или неудаче задачи. Не забудьте игнорировать ошибки, если хотите, чтобы Ansible продолжал выполнение на хосте при возникновении ошибки:
tasks:
- name: Register a variable, ignore errors and continue
ansible.builtin.command: /bin/false
register: result
ignore_errors: true
- name: Run only if the task that registered the "result" variable fails
ansible.builtin.command: /bin/something
when: result is failed
- name: Run only if the task that registered the "result" variable succeeds
ansible.builtin.command: /bin/something_else
when: result is succeeded
- name: Run only if the task that registered the "result" variable is skipped
ansible.builtin.command: /bin/still/something_else
when: result is skipped
Примечание
Старые версии Ansible использовали success и fail, но succeeded и failed используют правильное время. Все эти варианты сейчас допустимы.
Условные операторы на основе переменных
Вы также можете создавать условные операторы на основе переменных, определенных в плейбуках или инвентаризации. Поскольку условные операторы требуют булевых значений (тест должен быть оценён как True, чтобы вызвать условие), вы должны применить фильтр | bool к переменным, которые не являются булевыми, например, к строковым переменным с содержимым вроде ‘yes’, ‘on’, ‘1’ или ‘true’. Вы можете определить переменные таким образом:
vars: epic: true monumental: "yes"
При указанных выше переменных Ansible выполнит одну из этих задач и пропустит другую:
tasks:
- name: Run the command if "epic" or "monumental" is true
ansible.builtin.shell: echo "This certainly is epic!"
when: epic or monumental | bool
- name: Run the command if "epic" is false
ansible.builtin.shell: echo "This certainly isn't epic!"
when: not epic
Если необходимая переменная не задана, вы можете пропустить или отклонить её, используя тест Jinja2 defined. Например:
tasks:
- name: Run the command if "foo" is defined
ansible.builtin.shell: echo "I've got '{{ foo }}' and am not afraid to use it!"
when: foo is defined
- name: Fail if "bar" is undefined
ansible.builtin.fail: msg="Bailing out. This play requires 'bar'"
when: bar is undefined
Это особенно полезно в сочетании с условным импортом файлов vars (см. ниже). Как показывают примеры, вам не нужно использовать {{ }} для использования переменных внутри условных операторов, так как они уже подразумеваются.
Использование условных операторов в циклах
Если вы объединяете оператор when с циклом, Ansible обрабатывает условие отдельно для каждого элемента. Это сделано по умолчанию, чтобы вы могли выполнить задачу для некоторых элементов в цикле и пропустить её для других. Например:
tasks:
- name: Run with items greater than 5
ansible.builtin.command: echo {{ item }}
loop: [ 0, 2, 4, 6, 8, 10 ]
when: item > 5
Если вам нужно пропустить всю задачу, когда переменная цикла не определена, используйте фильтр |default для предоставления пустого итератора. Например, при переборе списка:
- name: Skip the whole task when a loop variable is undefined
ansible.builtin.command: echo {{ item }}
loop: "{{ mylist|default([]) }}"
when: item > 5
Вы можете сделать то же самое при переборе словаря:
- name: The same as above using a dict
ansible.builtin.command: echo {{ item.key }}
loop: "{{ query('dict', mydict|default({})) }}"
when: item.value > 5
Загрузка пользовательских фактов
Вы можете предоставить свои собственные факты, как описано в Разработка модулей. Чтобы запустить их, просто вызовите свой собственный модуль сбора фактов вверху списка задач, и переменные, возвращенные там, будут доступны для будущих задач:
tasks:
- name: Gather site specific fact data
action: site_facts
- name: Use a custom fact
ansible.builtin.command: /usr/bin/thingy
when: my_custom_fact_just_retrieved_from_the_remote_system == '1234'
Условные операторы с повторным использованием
Вы можете использовать условные операторы с переиспользуемыми файлами задач, плейбуками или ролями. Ansible обрабатывает эти условные операторы по-разному для динамического повторного использования (включения) и для статического повторного использования (импорта). Подробнее о повторном использовании в Ansible см. в разделе Повторное использование артефактов Ansible.
Условные операторы с импортами
Когда вы добавляете условный оператор к оператору импорта, Ansible применяет условие ко всем задачам в импортируемом файле. Это поведение эквивалентно Наследование тегов: добавление тегов к нескольким задачам. Ansible применяет условие к каждой задаче и оценивает каждую задачу отдельно. Например, у вас может быть плейбук, названный main.yml и файл задач, названный other_tasks.yml:
# all tasks within an imported file inherit the condition from the import statement
# main.yml
- import_tasks: other_tasks.yml # note "import"
when: x is not defined
# other_tasks.yml
- name: Set a variable
ansible.builtin.set_fact:
x: foo
- name: Print a variable
ansible.builtin.debug:
var: x
Ansible расширяет это во время выполнения до эквивалента:
- name: Set a variable if not defined
ansible.builtin.set_fact:
x: foo
when: x is not defined
# this task sets a value for x
- name: Do the task if "x" is not defined
ansible.builtin.debug:
var: x
when: x is not defined
# Ansible skips this task, because x is now defined
Таким образом, если x изначально не определено, задача debug будет пропущена. Если это не то поведение, которое вы хотите, используйте оператор include_* для применения условия только к этой задаче.
Вы можете применять условия к import_playbook так же, как и к другим import_* операторам. Когда вы используете этот подход, Ansible возвращает сообщение «skipped» для каждой задачи на каждом хосте, который не соответствует критериям, создавая повторяющийся вывод. Во многих случаях модуль group_by может быть более эффективным способом достижения той же цели; см. Обработка различий между ОС и дистрибутивами.
Условные включения
Когда вы используете условное выражение для оператора include_* , условие применяется только к самой задаче включения, а не к другим задачам в включённом файле(ах). Чтобы проиллюстрировать разницу с примером применения условий к импортированию, рассмотрим тот же плейбук и файл задач, но с использованием включения вместо импорта:
# Includes let you re-use 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
- name: Set a variable
ansible.builtin.set_fact:
x: foo
- name: Print a variable
ansible.builtin.debug:
var: x
Ansible расширяет это во время выполнения до эквивалента:
# main.yml
- include_tasks: other_tasks.yml
when: x is not defined
# if condition is met, Ansible includes other_tasks.yml
# other_tasks.yml
- name: Set a variable
ansible.builtin.set_fact:
x: foo
# no condition applied to this task, Ansible sets the value of x to foo
- name: Print a variable
ansible.builtin.debug:
var: x
# no condition applied to this task, Ansible prints the debug statement
Используя include_tasks вместо import_tasks, обе задачи из other_tasks.yml будут выполнены как ожидается. Дополнительную информацию о различиях между include и import см. в Использование Ansible-артефактов повторно.
Условные операторы с ролями
Существует три способа применения условий к ролям:
- Добавьте одно и то же условие или условия ко всем задачам в роли, поместив оператор
whenпод ключевым словомroles. См. пример в этом разделе. - Добавьте одно и то же условие или условия ко всем задачам в роли, поместив оператор
whenв статическуюimport_roleв вашем плейбуке. - Добавьте условие или условия к отдельным задачам или блокам внутри роли. Это единственный подход, который позволяет вам выбрать или пропустить некоторые задачи в роли на основе вашего оператора
when. Чтобы выбрать или пропустить задачи внутри роли, вы должны установить условия на отдельные задачи или блоки, использовать динамическуюinclude_roleв вашем плейбуке и добавить условие или условия к include. При использовании этого подхода Ansible применяет условие к самому включению, а также к любым задачам в роли, в которых также есть операторwhen.
Когда вы статически включаете роль в свой плейбук с помощью ключевого слова roles, Ansible добавляет определённые вами условия ко всем задачам в роли. Например:
- hosts: webservers
roles:
- role: debian_stock_config
when: ansible_facts['os_family'] == 'Debian'
Выбор переменных, файлов или шаблонов на основе фактов
Иногда факты о хосте определяют значения, которые вы хотите использовать для некоторых переменных или даже файл или шаблон, который вы хотите выбрать для этого хоста. Например, имена пакетов различаются в CentOS и Debian. Файлы конфигурации для общих сервисов также различаются в зависимости от различных вариантов ОС и версий. Чтобы загрузить разные файлы переменных, шаблоны или другие файлы на основе факта о хостах:
- назовите ваши файлы vars, шаблоны или файлы так, чтобы они соответствовали факту Ansible, который их различает
- выберите правильный файл vars, шаблон или файл для каждого хоста с помощью переменной, основанной на этом факте Ansible
Ansible отделяет переменные от задач, не допуская превращения ваших плейбуков в произвольный код с вложенными условиями. Этот подход приводит к более структурированным и проверяемым правилам конфигурации, поскольку точек принятия решений меньше.
Выбор файлов переменных на основе фактов
Вы можете создать плейбук, который работает на нескольких платформах и версиях ОС с минимальным количеством синтаксиса, поместив значения переменных в файлы vars и условно импортируя их. Если вы хотите установить Apache на некоторых серверах CentOS и некоторые Debian, создайте файлы переменных с ключами и значениями YAML. Например:
--- # for vars/RedHat.yml apache: httpd somethingelse: 42
Затем импортируйте эти файлы переменных, основываясь на фактах, которые вы собираете о хостах в своём плейбуке:
---
- hosts: webservers
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
ansible.builtin.service:
name: '{{ apache }}'
state: started
Ansible собирает факты о хостах в группе webservers, затем интерполирует переменную «ansible_facts[‘os_family’]» в список имён файлов. Если у вас есть хосты с операционными системами Red Hat (например, CentOS), Ansible ищет «vars/RedHat.yml». Если этот файл не существует, Ansible пытается загрузить «vars/os_defaults.yml». Для хостов Debian Ansible сначала ищет «vars/Debian.yml», а затем возвращается к «vars/os_defaults.yml». Если ни один из файлов в списке не найден, Ansible выводит ошибку.
Выбор файлов и шаблонов на основе фактов
Вы можете использовать тот же подход, когда различные варианты ОС или версии требуют различных файлов конфигурации или шаблонов. Выберите соответствующий файл или шаблон на основе переменных, присвоенных каждому хосту. Этот подход часто намного чище, чем помещать большое количество условных операторов в один шаблон для покрытия нескольких версий ОС или пакетов.
Например, вы можете создать шаблон файла конфигурации, который сильно отличается между, скажем, CentOS и Debian:
- name: Template a file
ansible.builtin.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/']
Часто используемые факты
Следующие факты Ansible часто используются в условных операторах.
ansible_facts[‘distribution’]
Возможные значения (пример, неполный список):
Alpine Altlinux Amazon Archlinux ClearLinux Coreos CentOS Debian Fedora Gentoo Mandriva NA OpenWrt OracleLinux RedHat Slackware SLES 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
См. также
- Работа с плейбуками
-
Введение в плейбуки
- Роли
-
Организация плейбуков по ролям
- Советы и рекомендации
-
Советы и рекомендации для плейбуков
- Использование переменных
-
Всё о переменных
- Список рассылки пользователей
-
Есть вопросы? Заходите на форум!
- irc.freenode.net
-
Чат-канал #ansible IRC
© 2012–2018 Michael DeHaan
© 2018–2021 Red Hat, Inc.
Licensed under the GNU General Public License version 3.
https://docs.ansible.com/ansible/2.11/user_guide/playbooks_conditionals.html