Spec-Zone.ru › Ansible 2.7

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

  • Оператор When
  • Циклы и условные операторы
  • Загрузка пользовательских фактов
  • Применение ‘when’ к ролям, импортам и включениям
  • Условные импорты
  • Выбор файлов и шаблонов на основе переменных
  • Регистрация переменных
  • Часто используемые факты
    • ansible_facts[‘distribution’]
    • ansible_facts[‘distribution_major_version’]
    • ansible_facts[‘os_family’]

Часто результат выполнения задачи зависит от значения переменной, факта (данных о удалённой системе) или результата предыдущей задачи. В некоторых случаях значения переменных могут зависеть от других переменных. Дополнительные группы могут быть созданы для управления хостами на основе соответствия хостов другим критериям. Эта тема описывает использование условных операторов в playbook.

Примечание

Существует множество способов управления потоком выполнения в Ansible. Дополнительные примеры поддерживаемых условных операторов можно найти здесь: http://jinja.pocoo.org/docs/dev/templates/#comparisons.

Оператор When

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

Это легко сделать в Ansible с помощью when оператора, который содержит необработанное выражение Jinja2 без двойных фигурных скобок (см. group_by — создание групп Ansible на основе фактов). На самом деле это довольно просто:

tasks:
  - name: "shut down Debian flavored systems"
    command: /sbin/shutdown -t now
    when: ansible_facts['os_family'] == "Debian"
    # note that all variables can be directly in conditionals without double curly braces

Вы также можете использовать скобки для группировки условий:

tasks:
  - name: "shut down CentOS 6 and Debian 7 systems"
    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")

Несколько условий, которые должны быть истинными одновременно (логическое «и»), также могут быть указаны как список:

tasks:
  - name: "shut down CentOS 6 systems"
    command: /sbin/shutdown -t now
    when:
      - ansible_facts['distribution'] == "CentOS"
      - ansible_facts['distribution_major_version'] == "6"

В операторах when также можно использовать ряд «тестов» и «фильтров» Jinja2, некоторые из которых уникальны и предоставляются Ansible. Предположим, мы хотим проигнорировать ошибку одного оператора, а затем решить что-то сделать условно в зависимости от успеха или неудачи:

tasks:
  - command: /bin/false
    register: result
    ignore_errors: True

  - command: /bin/something
    when: result is failed

  # In older versions of ansible use ``success``, now both are valid but succeeded uses the correct tense.
  - command: /bin/something_else
    when: result is succeeded

  - command: /bin/still/something_else
    when: result is skipped

Примечание

как success , так и succeeded работают (fail/failed, и т.д.).

Чтобы узнать, какие факты доступны на определённой системе, вы можете сделать следующее в playbook:

- debug: var=ansible_facts

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

tasks:
  - shell: echo "only on Red Hat 6, derivatives, and later"
    when: ansible_facts['os_family'] == "RedHat" and ansible_facts['lsb']['major_release']|int >= 6

Примечание

В приведённом выше примере требуется пакет lsb_release на целевом хосте для возврата факта ‘lsb major_release’.

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

vars:
  epic: true

Затем условное выполнение может выглядеть следующим образом:

tasks:
    - shell: echo "This certainly is epic!"
      when: epic

или:

tasks:
    - shell: echo "This certainly isn't epic!"
      when: not epic

Если необходимая переменная не задана, вы можете пропустить или прервать выполнение, используя Jinja2’s defined test. Например:

tasks:
    - shell: echo "I've got '{{ foo }}' and am not afraid to use it!"
      when: foo is defined

    - fail: msg="Bailing out. this play requires 'bar'"
      when: bar is undefined

Это особенно полезно в сочетании с условным импортом файлов vars (см. ниже). Как показывают примеры, вам не нужно использовать {{ }} для использования переменных в условных операторах, так как они уже подразумеваются.

Циклы и условные операторы

Комбинируя when с циклами (см. Циклы), имейте в виду, что when оператор обрабатывается отдельно для каждого элемента. Это сделано по дизайну:

tasks:
    - command: echo {{ item }}
      loop: [ 0, 2, 4, 6, 8, 10 ]
      when: item > 5

Если нужно пропустить всю задачу в зависимости от определения переменной цикла, используйте |default фильтр для предоставления пустого итератора:

- command: echo {{ item }}
  loop: "{{ mylist|default([]) }}"
  when: item > 5

Если используется словарь в цикле:

- command: echo {{ item.key }}
  loop: "{{ query('dict', mydict|default({})) }}"
  when: item.value > 5

Загрузка пользовательских фактов

Также легко предоставить собственные факты, если нужно, что описано в Разработка модуля. Чтобы запустить их, просто выполните вызов своего собственного модуля сбора фактов вверху вашего списка задач, и переменные, возвращённые там, будут доступны для последующих задач:

tasks:
    - name: gather site specific fact data
      action: site_facts
    - command: /usr/bin/thingy
      when: my_custom_fact_just_retrieved_from_the_remote_system == '1234'

Применение ‘when’ к ролям, импортам и включениям

Обратите внимание, что если у вас есть несколько задач, которые все используют одно и то же условное выражение, вы можете добавить это условие к инструкции включения задачи, как показано ниже. Все задачи будут оценены, но условие применяется к каждой отдельной задаче:

- import_tasks: tasks/sometasks.yml
  when: "'reticulating splines' in output"

Примечание

В версиях до 2.0 это работало с включениями задач, но не с включениями playbook. Версия 2.0 позволяет это использовать в обоих случаях.

Или с ролью:

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

Вы заметите, что по умолчанию в Ansible появляется много вывода «skipped» при использовании этого подхода на системах, которые не соответствуют критериям. Во многих случаях модуль group_by (см. Работа с модулями) может быть более эффективным способом достижения той же цели; см. Отличия в операционных системах и дистрибутивах.

Когда условие используется с include_* задачами вместо импортов, оно применяется only к самой задаче включения, а не к другим задачам в включённом файле(ах). Типичный случай, когда это различие важно, следующий:

# We wish to include a file to define a variable when it is not
# already defined

# main.yml
- import_tasks: other_tasks.yml # note "import"
  when: x is not defined

# other_tasks.yml
- set_fact:
    x: foo
- debug:
    var: x

Это расширяется во время включения до эквивалента:

- set_fact:
    x: foo
  when: x is not defined
- debug:
    var: x
  when: x is not defined

Таким образом, если x изначально не определено, задача debug будет пропущена. Используя include_tasks вместо import_tasks, обе задачи из other_tasks.yml будут выполнены как ожидается.

Дополнительную информацию о различиях между include и import см. в Создание повторно используемых playbooks.

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

Примечание

Это сложная тема, которая используется редко.

Иногда в playbook вы хотите делать разные вещи в зависимости от определённых критериев. Примером может служить один playbook, работающий на нескольких платформах и версиях ОС.

В качестве примера имя пакета Apache может отличаться между CentOS и Debian, но это легко обрабатывается с минимальной синтаксической нагрузкой в Ansible Playbook:

---
- hosts: all
  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
    service: name={{ apache }} state=started

Примечание

Переменная «ansible_facts[‘os_family’]» интерполируется в список имён файлов, определяемых для vars_files.

Напоминаем, что различные YAML-файлы содержат только ключи и значения:

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

Как это работает? Для операционных систем Red Hat (‘CentOS’, например) Ansible пытается импортировать первый файл ‘vars/RedHat.yml’. Если этот файл не существует, Ansible пытается загрузить ‘vars/os_defaults.yml’. Если файлы в списке не найдены, возникает ошибка.

В Debian Ansible сначала ищет ‘vars/Debian.yml’ вместо ‘vars/RedHat.yml’, прежде чем перейти к ‘vars/os_defaults.yml’.

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

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

Примечание

Это сложная тема, которая используется редко. Вы можете пропустить этот раздел.

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

Следующий пример демонстрирует, как создать шаблон конфигурационного файла, сильно различающегося между, например, CentOS и Debian:

- name: template a file
  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/']

Регистрация переменных

Часто в playbook может быть полезно сохранить результат определённой команды в переменной и получить к ней доступ позднее. Использование модуля команды таким образом во многих отношениях может устранить необходимость писать специфичные для сайта факты, например, вы можете проверить наличие определённой программы.

Ключевое слово ‘register’ определяет, в какой переменной сохранить результат. Результатные переменные могут быть использованы в шаблонах, строках действий или в операторах when. Это выглядит так (в явно тривиальном примере):

- name: test play
  hosts: all

  tasks:

      - shell: cat /etc/motd
        register: motd_contents

      - shell: echo "motd contains the word hi"
        when: motd_contents.stdout.find('hi') != -1

Как показано ранее, содержимое зарегистрированной переменной доступно с помощью значения ‘stdout’. Регистрируемый результат может быть использован в цикле задачи, если он преобразован в список (или уже является списком), как показано ниже. «stdout_lines» также доступен в объекте, хотя вы также можете вызвать «home_dirs.stdout.split()», если хотите, и можете разделить по другим полям:

- name: registered variable usage as a loop list
  hosts: all
  tasks:

    - name: retrieve the list of home directories
      command: ls /home
      register: home_dirs

    - name: add home dirs to the backup spooler
      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
        command: ls mydir
        register: contents

      - name: check contents for emptiness
        debug:
          msg: "Directory is empty"
        when: contents.stdout == ""

Часто используемые факты

Следующие факты часто используются в условных операторах — см. примеры выше.

ansible_facts[‘distribution’]

Возможные значения (пример, неполный список):

Alpine
Altlinux
Amazon
Archlinux
ClearLinux
Coreos
Debian
Fedora
Gentoo
Mandriva
NA
OpenWrt
OracleLinux
RedHat
Slackware
SMGL
SUSE
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

См. также

Работа с playbook
Введение в playbook
Роли
Организация playbook по ролям
Рекомендации по наилучшей практике
Рекомендации по наилучшей практике в playbook
Использование переменных
Все о переменных
Список рассылки пользователей
Есть вопрос? Зайдите на форум Google!
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.7/user_guide/playbooks_conditionals.html

Spec-Zone.ru

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