Spec-Zone.ru › Ansible

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

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

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

Примечание

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

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

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

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

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

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

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

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

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

  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() }}"

Строковое содержимое зарегистрированной переменной может быть пустым. Если вы хотите выполнить другую задачу только на хостах, где вывод вашей зарегистрированной переменной пустой, проверьте пустоту строкового содержимого зарегистрированной переменной:

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

  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’]

Основная версия операционной системы. Например, значение равно 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

Spec-Zone.ru

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