Обработка данных
Во многих случаях вам нужно выполнить некоторые сложные операции с переменными, и хотя Ansible не рекомендуется как инструмент для обработки/манипулирования данными, вы можете использовать существующую систему шаблонизации Jinja2 в сочетании с множеством дополнительных фильтров, поисковых запросов и проверок Ansible для выполнения очень сложных преобразований.
- Давайте начнем с краткого определения каждого типа плагина:
-
- lookups: В основном используются для запроса «внешних данных», в Ansible они были основной частью циклов, использующих конструкцию
with_<lookup>, но они могут использоваться независимо для возвращения данных для обработки. Обычно они возвращают список из-за их основной функции в циклах, как упоминалось ранее. Используются с операторами Jinja2lookupилиquery. - filters: используются для изменения/преобразования данных, используются с оператором Jinja2
|. - tests: используются для проверки данных, используются с оператором Jinja2
is.
- lookups: В основном используются для запроса «внешних данных», в Ansible они были основной частью циклов, использующих конструкцию
Циклы и списковые включения
Большинство языков программирования имеют циклы (for, while и т. д.) и списковые включения для выполнения преобразований со списками, включая списки объектов. Jinja2 имеет несколько фильтров, предоставляющих эту функциональность: map, select, reject, selectattr, rejectattr.
- map: это базовый цикл for, который позволяет изменить каждый элемент в списке, используя ключевое слово «attribute», вы можете выполнить преобразование, основанное на атрибутах элементов списка.
- select/reject: это цикл for с условием, который позволяет создать подмножество списка, которое соответствует (или не соответствует) на основе результата условия.
- selectattr/rejectattr: очень похожи на вышеперечисленные, но для условного оператора используется определенный атрибут элементов списка.
Извлечение ключей из словаря, соответствующих элементам из списка
Эквивалентный код на 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
ok: [localhost] => {
"msg": [
"routed",
"bridged",
"routed",
"bridged"
]
}
Нахождение точки монтирования
В этом случае мы хотим найти точку монтирования для данного пути на наших машинах, поскольку мы уже собираем факты монтирования, мы можем использовать следующее:
- 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"
Другой способ — избежать добавления элементов в список в первую очередь, поэтому вы можете использовать его непосредственно:
- 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"
Преобразования сложных типов
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 списков:
- 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:
myvarnames: "{{ q('varnames', '^my') }}"
mydict: "{{ dict(myvarnames | zip(q('vars', *myvarnames))) }}"
Небольшое объяснение, так как из этих двух строк много чего нужно распаковать:
- Поисковый запрос
varnamesвозвращает список переменных, которые начинаются сmy. - Затем, передав список из предыдущего шага в поисковый запрос
vars, мы получаем список значений.*используется для «разъяснения списка» (прием, используемый в Python, который работает и в 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) }}
См. также
- Использование фильтров для манипулирования данными
-
Встроенные фильтры Jinja2 в Ansible
- Проверки
-
Встроенные тесты Jinja2 в Ansible
- Документация Jinja2
-
Документация Jinja2, включает списки основных фильтров и проверок
© 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/complex_data_manipulation.html