Spec-Zone.ru › Ansible 2.8

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

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

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

Примечание

Существует множество способов управления потоком выполнения в 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 used 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, и т. д.).

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

- 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 major_release’ требуется пакет lsb_release на целевом хосте.

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

vars:
  epic: true

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

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

или:

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

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

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’ к ролям, импортам и включениям

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

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

Примечание

В версиях до 2.0 это работало с включениями задач, но не с включениями задач Ansible. Версия 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 см. Создание многоразовых задач Ansible.

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

Примечание

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

Иногда в задаче Ansible вы захотите сделать определенные вещи по-разному в зависимости от определенных критериев. Хороший пример - использование одного playbook для нескольких платформ и версий ОС.

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

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

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

Примечание

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

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

Следующий пример показывает, как сгенерировать файл конфигурации, который существенно различается между 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/']

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

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

Примечание

Регистрация происходит даже если задача пропускается из-за условного оператора. Таким образом, вы можете проверить переменную на «is skipped», чтобы узнать, была ли задача попытка или нет.

Ключевое слово ‘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
Windows

См. также

Работа с playbook
Введение в playbooks
Роли
Организация playbooks по ролям
Рекомендации по практике
Рекомендации по практике в playbooks
Использование переменных
Все о переменных
Список рассылки пользователей
Есть вопрос? Зайдите на форум Google!
irc.freenode.net
#ansible канал IRC-чата

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

Spec-Zone.ru

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