Spec-Zone.ru › Ansible 2.10

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

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

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

Примечание

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

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

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

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

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

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

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

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

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

  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

Примечание

Старые версии 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.

Условные импорты

Когда вы добавляете условное выражение к инструкции import, Ansible применяет это условие ко всем задачам в импортируемом файле. Это поведение эквивалентно наследованию тегов: добавление тегов к нескольким задачам. Ansible применяет условие к каждой задаче и оценивает каждую задачу отдельно. Например, у вас может быть playbook под названием 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.builin.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_* , условие применяется только к самой задаче включения, а не к любым другим задачам в включенном файле(ах). Чтобы проиллюстрировать разницу с примером выше, использующим условные выражения для import, посмотрите на тот же playbook и файл задач, но с использованием include вместо import:

# 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 в вашем playbook.
  • Добавьте условие или условия к отдельным задачам или блокам внутри роли. Это единственный подход, который позволяет выбирать или пропускать некоторые задачи внутри роли на основе вашего when . Для выбора или пропуска задач внутри роли необходимо установить условия на отдельные задачи или блоки, использовать динамический include_role в вашем playbook и добавить условие или условия к include. При использовании этого подхода Ansible применяет условие к самому include, а также к любым задачам в роли, у которых также есть инструкция when.

Когда вы включаете роль в свой playbook статически с ключевым словом roles , Ansible добавляет определенные вами условия ко всем задачам в роли. Например:

- hosts: webservers
  roles:
     - role: debian_stock_config
       when: ansible_facts['os_family'] == 'Debian'

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

Иногда факты о хосте определяют значения, которые вы хотите использовать для определенных переменных или даже файл или шаблон, который вы хотите выбрать для этого хоста. Например, имена пакетов отличаются в CentOS и Debian. Конфигурационные файлы для общих служб также отличаются в разных вариантах и версиях операционных систем. Чтобы загрузить разные файлы переменных, шаблоны или другие файлы на основе факта о хостах:

  1. назовите файлы vars, шаблоны или файлы так, чтобы они соответствовали факту Ansible, который их различает
  2. выберите правильный файл vars, шаблон или файл для каждого хоста с помощью переменной, основанной на этом факте Ansible

Ansible отделяет переменные от задач, не допуская превращения ваших playbooks в произвольный код с вложенными условиями. Это приводит к более структурированным и контролируемым правилам конфигурации, потому что точек принятия решений меньше.

Выбор файлов переменных на основе фактов

Вы можете создать playbook, который работает на нескольких платформах и версиях операционных систем с минимальным количеством синтаксиса, поместив значения переменных в файлы vars и условно импортируя их. Если вы хотите установить Apache на некоторых серверах CentOS и некоторых серверах Debian, создайте файлы переменных с ключами и значениями YAML. Например:

---
# for vars/RedHat.yml
apache: httpd
somethingelse: 42

Затем импортируйте эти файлы переменных на основе фактов, которые вы собираете о хостах в своем playbook:

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

См. также

Работа с playbooks

Вступление к playbooks

Роли

Организация playbook по ролям

Советы и рекомендации

Советы и рекомендации для playbooks

Использование переменных

Все о переменных

Список рассылки пользователей

У вас есть вопросы? Заходите на форум!

irc.freenode.net

IRC-чат-канал #ansible

© 2012–2018 Michael DeHaan
© 2018–2019 Red Hat, Inc.
Licensed under the GNU General Public License version 3.
https://docs.ansible.com/ansible/2.10/user_guide/playbooks_conditionals.html

Spec-Zone.ru

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