Условные операторы
В плейбуке может потребоваться выполнять разные задачи или преследовать разные цели в зависимости от значения факта (данных об удалённой системе), переменной или результата предыдущей задачи. Возможно, вы захотите, чтобы значение некоторых переменных зависело от значения других переменных. Или вы захотите создать дополнительные группы хостов в зависимости от того, соответствуют ли хосты другим критериям. Всё это можно сделать с помощью условных операторов.
Ansible использует тесты и фильтры Jinja2 в условных операторах. Ansible поддерживает все стандартные тесты и фильтры, а также добавляет несколько уникальных.
Примечание
В Ansible есть множество способов управлять потоком выполнения. Дополнительные примеры поддерживаемых условных операторов можно найти по адресу https://jinja.palletsprojects.com/en/latest/templates/#comparisons.
- Отладка условных операторов
Основные условные операторы с when
Самый простой условный оператор применяется к одной задаче. Создайте задачу, а затем добавьте оператор when, применяющий тест. Предложение when — это необработанное выражение Jinja2 без двойных фигурных скобок (см. раздел Обращение к простым переменным). При запуске задачи или плейбука 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
Часто требуется выполнить или пропустить задачу на основе фактов. Факты — это атрибуты отдельных хостов, включая 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() }}"
Строковое содержимое зарегистрированной переменной может быть пустым. Если нужно выполнить другую задачу только на хостах, где 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
- 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
Если обязательная переменная не задана, можно пропустить задачу или завершить её с ошибкой, используя тест defined из Jinja2. Например:
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 будет пропущена, поскольку условие проверяется для каждой импортированной задачи. Для задачи set_fact условие будет иметь значение true, что позволит определить переменную и приведёт к тому, что условие 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’]
Основная версия операционной системы. Например, для Ubuntu 16.04 значение равно 16.
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–2025 Red Hat, Inc.
Licensed under the GNU General Public License version 3.
https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_conditionals.html