Использование Ansible set_fact для создания словаря из результатов регистрации
В Ansible я использовал register
для сохранения результатов задачи в переменной people
. Опуская материал, который мне не нужен, он имеет следующую структуру:
{
"results": [
{
"item": {
"name": "Bob"
},
"stdout": "male"
},
{
"item": {
"name": "Thelma"
},
"stdout": "female"
}
]
}
Я хотел бы использовать следующую задачу set_fact
для создания новой переменной со следующим словарем:
{
"Bob": "male",
"Thelma": "female"
}
Я предполагаю, что это может быть возможно, но я собираюсь кругами без везения.
Ответы
Ответ 1
Я думаю, что добрался до конца.
Задача такова:
- name: Populate genders
set_fact:
genders: "{{ genders|default({}) | combine( {item.item.name: item.stdout} ) }}"
with_items: "{{ people.results }}"
Он проходит через каждый из dicts (item
) в массиве people.results
, каждый раз создавая новый dict вроде {Bob: "male"}
и combine()
, который новый dict в массиве genders
, который заканчивается как:
{
"Bob": "male",
"Thelma": "female"
}
Предполагается, что ключи (name
в этом случае) будут уникальными.
Тогда я понял, что мне действительно нужен список словарей, так как кажется, что гораздо проще выполнить цикл с помощью with_items
:
- name: Populate genders
set_fact:
genders: "{{ genders|default([]) + [ {'name': item.item.name, 'gender': item.stdout} ] }}"
with_items: "{{ people.results }}"
Это позволяет комбинировать существующий список со списком, содержащим один dict. Мы получаем массив genders
следующим образом:
[
{'name': 'Bob', 'gender': 'male'},
{'name': 'Thelma', 'gender': 'female'}
]
Ответ 2
Спасибо Фил за ваше решение; в случае, если кто-то попадает в ту же ситуацию, что и я, вот (более сложный) вариант:
---
# this is just to avoid a call to |default on each iteration
- set_fact:
postconf_d: {}
- name: 'get postfix default configuration'
command: 'postconf -d'
register: command
# the answer of the command give a list of lines such as:
# "key = value" or "key =" when the value is null
- name: 'set postfix default configuration as fact'
set_fact:
postconf_d: >
{{
postconf_d |
combine(
dict([ item.partition('=')[::2]|map('trim') ])
)
with_items: command.stdout_lines
Это даст следующий вывод (снятый для примера):
"postconf_d": {
"alias_database": "hash:/etc/aliases",
"alias_maps": "hash:/etc/aliases, nis:mail.aliases",
"allow_min_user": "no",
"allow_percent_hack": "yes"
}
Идя еще дальше, проанализируйте списки в "значении":
- name: 'set postfix default configuration as fact'
set_fact:
postconf_d: >-
{% set key, val = item.partition('=')[::2]|map('trim') -%}
{% if ',' in val -%}
{% set val = val.split(',')|map('trim')|list -%}
{% endif -%}
{{ postfix_default_main_cf | combine({key: val}) }}
with_items: command.stdout_lines
...
"postconf_d": {
"alias_database": "hash:/etc/aliases",
"alias_maps": [
"hash:/etc/aliases",
"nis:mail.aliases"
],
"allow_min_user": "no",
"allow_percent_hack": "yes"
}
Несколько замечаний:
-
в этом случае ему нужно было "обрезать" все (используя >-
в YAML и -%}
в Jinja), в противном случае вы получите сообщение об ошибке:
FAILED! => {"failed": true, "msg": "|combine expects dictionaries, got u\" {u'...
-
очевидно, что {% if ..
далек от пуленепробиваемого
-
в постфиксном случае val.split(',')|map('trim')|list
можно было бы упростить до val.split(', ')
, но я хотел указать на то, что вам нужно будет |list
, иначе вы получите сообщение об ошибке:
"|combine expects dictionaries, got u\"{u'...': <generator object do_map at ...
Надеюсь, это поможет.