Использование Ansible set_fact для создания словаря из результатов регистра

В Ansible я использовал register сохранить результаты задачи в переменной people, Опуская материал, который мне не нужен, он имеет такую ​​структуру:

{
    "results": [
        {
            "item": {
                "name": "Bob"
            },
            "stdout": "male"
        },
        {
            "item": {
                "name": "Thelma"
            },
            "stdout": "female"
        }
    ]
}

Я хотел бы использовать последующий set_fact Задача создать новую переменную со словарем:

{
    "Bob": "male",
    "Thelma": "female"
}

Я думаю, что это возможно, но я иду кругами, но пока безуспешно.

2 ответа

Решение

Я думаю, что я добрался там в конце.

Задача такова:

- name: Populate genders
  set_fact:
    genders: "{{ genders|default({}) | combine( {item.item.name: item.stdout} ) }}"
  with_items: "{{ people.results }}"

Это проходит через каждый из диктов (item) в people.results массив, каждый раз создавая новый dict как {Bob: "male"}, а также combine()Это новый диктат в 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 }}"

Это продолжает объединять существующий список со списком, содержащим один диктант. Мы заканчиваем с genders массив, как это:

[
    {'name': 'Bob', 'gender': 'male'},
    {'name': 'Thelma', 'gender': 'female'}
]

Спасибо, Фил, за ваше решение; на случай, если кто-нибудь попадет в ту же ситуацию, что и я, вот (более сложный) вариант:

---
# 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"
}

Если пойти еще дальше, проанализируйте списки в 'value':

- 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"
}

Несколько вещей, на которые стоит обратить внимание:

  • в этом случае нужно все "обрезать" (используя >- в ЯМЛ и -%} в 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 ...
    

Надеюсь, это поможет.

Другие вопросы по тегам