Spec-Zone.ru › Ansible 2.4

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

  • Условное выполнение (When Statement)
  • Циклы и условные операторы
  • Загрузка пользовательских фактов
  • Применение `when` к ролям, импортам и включениям
  • Условные импорты
  • Выбор файлов и шаблонов на основе переменных
  • Регистрация переменных

Часто результат выполнения задачи зависит от значения переменной, факта (информация о удалённой системе) или результата предыдущей задачи. В некоторых случаях значения переменных могут зависеть от других переменных. Кроме того, можно создавать дополнительные группы для управления хостами на основе соответствия хостов определённым критериям. Ansible предоставляет множество возможностей для управления потоком выполнения. Дополнительные примеры поддерживаемых условных операторов можно найти здесь: http://jinja.pocoo.org/docs/dev/templates/#comparisons

Давайте углубимся в их использование.

Условное выполнение (When Statement)

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

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

tasks:
  - name: "shut down Debian flavored systems"
    command: /sbin/shutdown -t now
    when: ansible_os_family == "Debian"
    # note that Ansible facts and vars like ansible_os_family 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_distribution == "CentOS" and ansible_distribution_major_version == "6") or
          (ansible_distribution == "Debian" and ansible_distribution_major_version == "7")

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

tasks:
  - name: "shut down CentOS 6 systems"
    command: /sbin/shutdown -t now
    when:
      - ansible_distribution == "CentOS"
      - ansible_distribution_major_version == "6"

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

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

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

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

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

Примечание

фильтры были обновлены в версии 2.1, поэтому оба `success` и `succeeded` работают (`fail`/`failed, и т. д.).

Обратите внимание, что это лёгкое предвосхищение оператора «register». Мы вернёмся к нему немного позже в этой главе.

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

ansible hostname.example.com -m setup

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

tasks:
  - shell: echo "only on Red Hat 6, derivatives, and later"
    when: ansible_os_family == "RedHat" and ansible_lsb.major_release|int >= 6

Примечание

в приведённом выше примере требуется пакет `lsb_release` на целевом хосте, чтобы вернуть факт `ansible_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 `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` с `with_items` (см. Циклы), имейте в виду, что оператор `when` обрабатывается отдельно для каждого элемента. Это сделано по умолчанию:

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

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

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

Если вы используете `with_dict`, который не принимает список:

- command: echo {{ item.key }}
  with_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_os_family == 'Debian' }

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

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

# include 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
- set_fact:
    x: foo
- debug:
    var: x

В приведенном выше примере, если `import_tasks` было использовано вместо этого, обе включенные задачи также были бы пропущены. С `include_tasks` вместо этого задачи выполняются как ожидается, потому что условное выражение к ним не применяется.

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

Примечание

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

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

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

---
- hosts: all
  remote_user: root
  vars_files:
    - "vars/common.yml"
    - [ "vars/{{ ansible_os_family }}.yml", "vars/os_defaults.yml" ]
  tasks:
  - name: make sure apache is started
    service: name={{ apache }} state=started

Примечание

Переменная `ansible_os_family` интерполируется в список имён файлов, определяемых для vars_files.

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

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

Как это работает? Если операционная система была «CentOS», Ansible первым попытался бы импортировать файл «vars/CentOS.yml», а затем «/vars/os_defaults.yml», если этот файл не существовал. Если файлы в списке не были найдены, было бы выведено сообщение об ошибке. На Debian Ansible первым искал бы «vars/Debian.yml», а затем «vars/os_defaults.yml». Довольно просто.

Для использования этой функции условного импорта вам нужно установить facter или ohai перед запуском playbook, но, конечно, вы можете сделать это с помощью Ansible, если захотите:

# for facter
ansible -m yum -a "pkg=facter state=present"
ansible -m yum -a "pkg=ruby-json state=present"

# for ohai
ansible -m yum -a "pkg=ohai state=present"

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

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

Примечание

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

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

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

- name: template a file
  template: src={{ item }} dest=/etc/myapp/foo.conf
  with_first_found:
    - files:
       - {{ ansible_distribution }}.conf
       - default.conf
      paths:
       - search_location_one/somedir/
       - /opt/other_location/somedir/

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

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

Ключевое слово «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`. Регистрируемый результат может быть использован в «with_items» задачи, если он преобразован в список (или уже является списком), как показано ниже. «stdout_lines» также доступен в объекте, хотя вы также можете вызвать `home_dirs.stdout.split()`, если хотите, и можете разделить по другим полям:

- name: registered variable usage as a with_items 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
        with_items: "{{ home_dirs.stdout_lines }}"
        # same as with_items: "{{ 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 == ""

См. также

Playbooks
Введение в playbooks
Роли
Организация playbook по ролям
Рекомендации по разработке
Рекомендации по разработке playbooks
Переменные
Всё о переменных
Список рассылки пользователей
У вас есть вопрос? Зайдите на группу 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.4/playbooks_conditionals.html

Spec-Zone.ru

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