Spec-Zone.ru › Ansible

Условные операторы

В плейбуке может потребоваться выполнять разные задачи или преследовать разные цели в зависимости от значения факта (данных об удалённой системе), переменной или результата предыдущей задачи. Возможно, вы захотите, чтобы значение некоторых переменных зависело от значения других переменных. Или вы захотите создать дополнительные группы хостов в зависимости от того, соответствуют ли хосты другим критериям. Всё это можно сделать с помощью условных операторов.

Ansible использует тесты и фильтры Jinja2 в условных операторах. Ansible поддерживает все стандартные тесты и фильтры, а также добавляет несколько уникальных.

Примечание

В Ansible есть множество способов управлять потоком выполнения. Дополнительные примеры поддерживаемых условных операторов можно найти по адресу https://jinja.palletsprojects.com/en/latest/templates/#comparisons.

  • Основные условные операторы с when

    • Условные операторы на основе фактов Ansible
    • Условия на основе зарегистрированных переменных
    • Условные операторы на основе переменных
    • Использование условных операторов в циклах
    • Загрузка пользовательских фактов
    • Условные операторы при повторном использовании

      • Условные операторы с импортами
      • Условные операторы с включениями
      • Условные операторы с ролями
    • Выбор переменных, файлов или шаблонов на основе фактов

      • Выбор файлов переменных на основе фактов
      • Выбор файлов и шаблонов на основе фактов
  • Отладка условных операторов
  • Часто используемые факты

    • ansible_facts[‘distribution’]
    • ansible_facts[‘distribution_major_version’]
    • ansible_facts[‘os_family’]

Основные условные операторы с 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"

Условия на основе зарегистрированных переменных

Часто в плейбуке требуется выполнить или пропустить задачу в зависимости от результата предыдущей задачи. Например, может потребоваться настроить службу после её обновления предыдущей задачей. Чтобы создать условие на основе зарегистрированной переменной:

  1. Зарегистрируйте результат предыдущей задачи в переменной.
  2. Создайте условный тест на основе зарегистрированной переменной.

Имя зарегистрированной переменной задаётся с помощью ключевого слова 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 различаются. Файлы конфигурации распространённых служб также отличаются в разных вариантах и версиях ОС. Чтобы загружать разные файлы переменных, шаблоны или другие файлы на основе факта о хостах:

  1. назовите файлы переменных, шаблоны или файлы в соответствии с фактом Ansible, который позволяет их различить
  2. выберите для каждого хоста подходящий файл переменных, шаблон или файл с помощью переменной, основанной на этом факте 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API