Spec-Zone.ru › Ansible 2.11

Обработка данных

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

Давайте начнем с краткого определения каждого типа плагина:
  • поиски: В основном используются для запроса «внешних данных», в Ansible они были основной частью циклов, использующих конструкцию with_<lookup>, но они могут использоваться независимо для возврата данных для обработки. Обычно они возвращают список из-за их основной функции в циклах, как упоминалось ранее. Используются с операторами Jinja2 lookup или query.
  • фильтры: используются для изменения/преобразования данных, используются с оператором Jinja2 |.
  • тесты: используются для проверки данных, используются с оператором Jinja2 is.

Циклы и списочные включения

Большинство языков программирования имеют циклы (for, while, и так далее) и списочные включения для преобразования списков, включая списки объектов. Jinja2 имеет несколько фильтров, которые обеспечивают эту функциональность: map, select, reject, selectattr, rejectattr.

  • map: это базовый цикл for, который просто позволяет вам изменить каждый элемент в списке, используя ключевое слово «attribute», вы можете выполнить преобразование, основанное на атрибутах элементов списка.
  • select/reject: это цикл for с условием, который позволяет создавать подмножество списка, которое соответствует (или нет) результату условия.
  • selectattr/rejectattr: очень похожи на предыдущие, но для условного оператора используется определенный атрибут элементов списка.

Используйте цикл для создания экспоненциальной задержки для повторных попыток/до.

- name: retry ping 10 times with exponential backup delay
  ping:
  retries: 10
  delay: '{{item|int}}'
  loop: '{{ range(1, 10)|map('pow', 2) }}'

Извлечение ключей из словаря, соответствующих элементам списка

Эквивалентный код на Python был бы:

chains = [1, 2]
for chain in chains:
    for config in chains_config[chain]['configs']:
        print(config['type'])

Существует несколько способов сделать это в Ansible, это просто один из примеров:

Способ извлечения соответствующих ключей из списка словарей
 tasks:
   - name: Show extracted list of keys from a list of dictionaries
     ansible.builtin.debug:
       msg: "{{ chains | map('extract', chains_config) | map(attribute='configs') | flatten | map(attribute='type') | flatten }}"
     vars:
       chains: [1, 2]
       chains_config:
           1:
               foo: bar
               configs:
                   - type: routed
                     version: 0.1
                   - type: bridged
                     version: 0.2
           2:
               foo: baz
               configs:
                   - type: routed
                     version: 1.0
                   - type: bridged
                     version: 1.1
Результаты задачи debug, список с извлеченными ключами
   ok: [localhost] => {
       "msg": [
           "routed",
           "bridged",
           "routed",
           "bridged"
       ]
   }
Получить уникальный список значений переменной, которая варьируется по хостам
   vars:
       unique_value_list: "{{ groups['all'] | map ('extract', hostvars, 'varname') | list | unique}}"

Найти точку монтирования

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

Использование selectattr для фильтрации монтирований в список, который я затем могу отсортировать и выбрать последний из
  - hosts: all
    gather_facts: True
    vars:
       path: /var/lib/cache
    tasks:
    - name: The mount point for {{path}}, found using the Ansible mount facts, [-1] is the same as the 'last' filter
      ansible.builtin.debug:
       msg: "{{(ansible_facts.mounts | selectattr('mount', 'in', path) | list | sort(attribute='mount'))[-1]['mount']}}"

Исключение элементов из списка

Специальная переменная omit РАБОТАЕТ ТОЛЬКО с параметрами модуля, но мы все равно можем использовать ее другими способами как идентификатор для настройки списка элементов:

Встроенная фильтрация списка при передаче параметра модуля
   - name: Enable a list of Windows features, by name
     ansible.builtin.set_fact:
       win_feature_list: "{{ namestuff | reject('equalto', omit) | list }}"
     vars:
       namestuff:
         - "{{ (fs_installed_smb_v1 | default(False)) | ternary(omit, 'FS-SMB1') }}"
         - "foo"
         - "bar"

Другой способ — не добавлять элементы в список в первую очередь, поэтому вы можете использовать его непосредственно:

Использование set_fact в цикле для условного наращивания списка
   - name: Build unique list with some items conditionally omitted
     ansible.builtin.set_fact:
        namestuff: ' {{ (namestuff | default([])) | union([item]) }}'
     when: item != omit
     loop:
         - "{{ (fs_installed_smb_v1 | default(False)) | ternary(omit, 'FS-SMB1') }}"
         - "foo"
         - "bar"

Настройка Fileglob на основе переменной

В этом примере используется распаковка списка аргументов Python для создания настраиваемого списка fileglob на основе переменной.

Использование fileglob со списком на основе переменной.
  - hosts: all
    vars:
      mygroups
        - prod
        - web
    tasks:
      - name: Copy a glob of files based on a list of groups
        copy:
          src: "{{ item }}"
          dest: "/tmp/{{ item }}"
        loop: '{{ q("fileglob", *globlist) }}'
        vars:
          globlist: '{{ mygroups | map("regex_replace", "^(.*)$", "files/\1/*.conf") | list }}'

Преобразования сложных типов

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

Создание словаря из списка

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

Эти примеры создают {"a": "b", "c": "d"}

Простой список в словарь, предполагая, что список имеет вид [ключ, значение, ключ, значение, …]
 vars:
     single_list: [ 'a', 'b', 'c', 'd' ]
     mydict: "{{ dict(single_list | slice(2) | list) }}"
Это проще, когда у нас есть список пар:
 vars:
     list_of_pairs: [ ['a', 'b'], ['c', 'd'] ]
     mydict: "{{ dict(list_of_pairs) }}"

Оба варианта в конечном итоге приводят к одному и тому же результату, при этом slice(2) | list преобразует single_list в ту же структуру, что и list_of_pairs.

Немного сложнее, используя set_fact и loop для создания/обновления словаря со значениями пар ключей из 2 списков:

Использование set_fact для создания словаря из набора списков
    - name: Uses 'combine' to update the dictionary and 'zip' to make pairs of both lists
      ansible.builtin.set_fact:
        mydict: "{{ mydict | default({}) | combine({item[0]: item[1]}) }}"
      loop: "{{ (keys | zip(values)) | list }}"
      vars:
        keys:
          - foo
          - var
          - bar
        values:
          - a
          - b
          - c

Это приводит к {"foo": "a", "var": "b", "bar": "c"}.

Вы даже можете объединить эти простые примеры с другими фильтрами и поисковыми запросами, чтобы динамически создать словарь, сопоставляя шаблоны с именами переменных:

Использование ‘vars’ для определения словаря из набора списков без необходимости в задаче
   vars:
       myvarnames: "{{ q('varnames', '^my') }}"
       mydict: "{{ dict(myvarnames | zip(q('vars', *myvarnames))) }}"

Краткое объяснение, так как здесь много чего нужно разобрать из этих двух строк:

  • Поиск varnames возвращает список переменных, которые соответствуют «начинаются с my».
  • Затем список из предыдущего шага подается на поиск vars для получения списка значений. * используется для «ссылочного доступа к списку» (pythonism, который работает в Jinja), в противном случае он принял бы список как один аргумент.
  • Оба списка передаются фильтру zip для объединения их в единый список (ключ, значение, ключ2, значение2, …).
  • Функция dict затем принимает этот «список пар», чтобы создать словарь.

Пример использования фактов для поиска данных хоста, которые соответствуют условию X:

vars:
  uptime_of_host_most_recently_rebooted: "{{ansible_play_hosts_all | map('extract', hostvars, 'ansible_uptime_seconds') | sort | first}}"

Используя пример от @zoradache на reddit, чтобы показать «время работы в днях/часах/минутах» (предполагается, что факты были собраны). https://www.reddit.com/r/ansible/comments/gj5a93/trying_to_get_uptime_from_seconds/fqj2qr3/

- name: Show the uptime in a certain format
  ansible.builtin.debug:
   msg: Timedelta {{ now() - now().fromtimestamp(now(fmt='%s') | int - ansible_uptime_seconds) }}

См. также

Использование фильтров для обработки данных

Включенные в Ansible фильтры Jinja2

Тесты

Включенные в Ansible тесты Jinja2

Документация Jinja2

Документация Jinja2, включает списки основных фильтров и тестов

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

Spec-Zone.ru

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