Условные операторы
В плейбуке вы можете выполнять различные задачи или иметь разные цели, в зависимости от значения факта (данных о удаленной системе), переменной или результата предыдущей задачи. Вы можете сделать значение некоторых переменных зависимым от значения других переменных. Или вы можете создать дополнительные группы хостов, основанные на том, соответствуют ли хосты другим критериям. Все это можно сделать с помощью условных операторов.
Ansible использует Jinja2 тесты и фильтры в условных операторах. Ansible поддерживает все стандартные тесты и фильтры и добавляет также некоторые уникальные.
Примечание
Существует много вариантов управления потоком выполнения в Ansible. Дополнительные примеры поддерживаемых условных операторов можно найти по адресу https://jinja.palletsprojects.com/en/latest/templates/#comparisons.
- Отладка условных операторов
Основные условные операторы с when
Простейшее условное утверждение применяется к одной задаче. Создайте задачу, затем добавьте утверждение when, которое применяет тест. Условие when — это исходное выражение Jinja2 без двойных фигурных скобок (см. group_by_module). При запуске задачи или плейбука 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: true
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 package установлен на целевом хосте. Чтобы увидеть, какие факты доступны на ваших системах, добавьте задачу 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
Вы можете сохранить факты Ansible в переменных для использования в условной логике, как в следующем примере:
tasks:
- name: Get the CPU temperature
set_fact:
temperature: "{{ ansible_facts['cpu_temperature'] }}"
- name: Restart the system if the temperature is too high
when: temperature | float > 90
shell: "reboot"
Условные операторы, основанные на зарегистрированных переменных
Часто в плейбуке вам нужно выполнить или пропустить задачу на основе результата ранее выполненной задачи. Например, вы можете настроить службу после её обновления предыдущей задачей. Чтобы создать условный оператор на основе зарегистрированной переменной:
- Зарегистрируйте результат предыдущей задачи как переменную.
- Создайте условный тест на основе зарегистрированной переменной.
Вы создаёте имя зарегистрированной переменной с помощью ключевого слова 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() }}"
Строковое содержимое зарегистрированной переменной может быть пустым. Если вы хотите выполнить другую задачу только на хостах, где вывод вашей зарегистрированной переменной пустой, проверьте пустоту строкового содержимого зарегистрированной переменной:
- 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
- name: Run only if the task that registered the "result" variable changed something.
ansible.builtin.command: /bin/still/something_else
when: result is changed
Примечание
Более старые версии 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
- hosts: all
tasks:
- 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 изначально определена, обе задачи пропустятся, как и предполагалось. Но если x изначально не определена, задача debug будет пропущена, так как условный оператор оценивается для каждой импортированной задачи. Условный оператор примет значение true для задачи set_fact, которая определит переменную и заставит условный оператор debug принять значение false.
Если это не то поведение, которое вам нужно, используйте оператор include_* , чтобы применить условие только к самому оператору.
# using a conditional on include_* only applies to the include task itself
# main.yml
- hosts: all
tasks:
- include_tasks: other_tasks.yml # note "include"
when: x is not defined
Теперь, если x изначально не определена, задача debug не будет пропущена, потому что условный оператор оценивается на момент включения и не применяется к отдельным задачам.
Вы можете применять условия к import_playbook так же, как и к другим операторам import_*. При использовании этого подхода Ansible возвращает сообщение «skipped» для каждой задачи на каждом хосте, не соответствующем критериям, создавая повторяющийся вывод. Во многих случаях модуль group_by может быть более эффективным способом достижения той же цели; см. Обработка различий в операционных системах.
Условные операторы с включением
Когда вы используете условный оператор в операторе include_* , условие применяется только к задаче включения, а не к другим задачам в включаемом файле(ах). Чтобы проиллюстрировать разницу с примером использования условных операторов с импортом выше, рассмотрим тот же плейбук и файл задач, но с включением вместо импорта:
# Includes let you reuse 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в вашем плейбуке и добавить условие или условия к оператору включения. При использовании этого подхода Ansible применяет условие к самому оператору включения, а также к любым задачам в роли, в которых также есть операторwhen.
Когда вы статически включаете роль в свой плейбук с помощью ключевого слова roles , Ansible добавляет определенные вами условия ко всем задачам в роли. Например:
- hosts: webservers
roles:
- role: debian_stock_config
when: ansible_facts['os_family'] == 'Debian'
Выбор переменных, файлов или шаблонов на основе фактов
Иногда факты о хосте определяют значения, которые вы хотите использовать для определенных переменных или даже файл или шаблон, который вы хотите выбрать для этого хоста. Например, имена пакетов отличаются в CentOS и Debian. Конфигурационные файлы для общих служб также отличаются в зависимости от дистрибутива и версии ОС. Чтобы загружать разные файлы переменных, шаблоны или другие файлы на основе факта о хостах:
- назовите файлы переменных, шаблоны или файлы таким образом, чтобы они соответствовали факту Ansible, который их различает
- выберите правильный файл переменных, шаблон или файл для каждого хоста с помощью переменной, основанной на этом факте Ansible
Ansible отделяет переменные от задач, предотвращая превращение ваших плейбуков в произвольный код с вложенными условными операторами. Этот подход приводит к более структурированным и проверяемым правилам конфигурации, так как точек принятия решений меньше.
Выбор файлов переменных на основе фактов
Вы можете создать плейбук, который работает на нескольких платформах и версиях ОС с минимальным синтаксисом, поместив значения переменных в файлы переменных и условно импортируя их. Если вы хотите установить 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/']
Отладка условных операторов
Если ваш условный оператор when ведет себя не так, как ожидается, вы можете добавить оператор debug , чтобы определить, принимает ли условие значение true или false. Распространённой причиной неожиданного поведения условных операторов является проверка целого числа как строки или строки как целого числа. Чтобы отладить условный оператор, добавьте весь оператор в качестве значения var: в задаче debug . Ansible затем покажет тест и как оценивается оператор. Например, вот набор задач и пример вывода:
- name: check value of return code
ansible.builtin.debug:
var: bar_status.rc
- name: check test for rc value as string
ansible.builtin.debug:
var: bar_status.rc == "127"
- name: check test for rc value as integer
ansible.builtin.debug:
var: bar_status.rc == 127
TASK [check value of return code] *********************************************************************************
ok: [foo-1] => {
"bar_status.rc": "127"
}
TASK [check test for rc value as string] **************************************************************************
ok: [foo-1] => {
"bar_status.rc == \"127\"": false
}
TASK [check test for rc value as integer] *************************************************************************
ok: [foo-1] => {
"bar_status.rc == 127": true
}
Часто используемые факты
Следующие факты 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 SMGL Slackware Solaris Suse Windows
См. также
- Работа с плейбуками
-
Введение в плейбуки
- Роли
-
Организация плейбуков по ролям
- Общие советы
-
Советы и рекомендации по плейбукам
- Использование переменных
-
Всё о переменных
- Связь
-
Есть вопросы? Нужна помощь? Хотите поделиться идеями? Посетите руководство по общению Ansible
© 2012–2018 Michael DeHaan
© 2018–2024 Red Hat, Inc.
Licensed under the GNU General Public License version 3.
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_conditionals.html