Ansible: Build reverse dictionary with duplicate values in the original dictionary

Viewed 227

I have a Ansible dictionary fact vm_templates looking like this:

 {
        "HostedEngine": "00000000-0000-0000-0000-000000000000", 
        "cpu-node0": "2d826ed8-1dbe-4b10-93a1-5ac9734462cb", 
        "cpu-node1": "2d826ed8-1dbe-4b10-93a1-5ac9734462cb", 
        "cpu-node2": "2d826ed8-1dbe-4b10-93a1-5ac9734462cb", 
        "cpu-node3": "2d826ed8-1dbe-4b10-93a1-5ac9734462cb", 
        "fp_gpu-node0": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "fp_gpu-node1": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "fp_gpu-node10": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "fp_gpu-node11": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "fp_gpu-node2": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "fp_gpu-node3": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "fp_gpu-node4": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "fp_gpu-node5": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "fp_gpu-node6": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "fp_gpu-node7": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "fp_gpu-node8": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "fp_gpu-node9": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "hp_gpu-node0": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "hp_gpu-node1": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "hp_gpu-node2": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "hp_gpu-node3": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "hp_gpu-node4": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "hp_gpu-node5": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "hp_gpu-node6": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "hp_gpu-node7": "a08901c0-50a5-4e2f-b6d7-b11b69a32613", 
        "infra-vm": "00000000-0000-0000-0000-000000000000"
    }

I would like to build a flipping dictionary from the above one, taking into account keys with duplicate values. So I would like to associate each value from the first dictionary with its keys in a list, and keys having duplicate values in the old dictionary will be in the same list value.

Here is the result I'm trying to get, using ansible :

 {
        "00000000-0000-0000-0000-000000000000": ["HostedEngine", "infra-vm"], 
        "2d826ed8-1dbe-4b10-93a1-5ac9734462cb": ["cpu-node0", "cpu-node1", "cpu-node2", "cpu-node3"], 
       "a08901c0-50a5-4e2f-b6d7-b11b69a32613": ["fp_gpu-node0", "fp_gpu-node1", "fp_gpu-node10", "fp_gpu-node11", "fp_gpu-node2", "fp_gpu-node3", "fp_gpu-node4", "fp_gpu-node5", "fp_gpu-node6", "fp_gpu-node7", "fp_gpu-node8", "fp_gpu-node9", "hp_gpu-node0", "hp_gpu-node1", "hp_gpu-node2", "hp_gpu-node3", "hp_gpu-node4", "hp_gpu-node5", "hp_gpu-node6", "hp_gpu-node7"]
 }

In python I would have written something like :

reverse_templates = {} 


for key, value in vm_template.items(): 
    if value not in reverse_templates: 
        reverse_templates[value] = [key] 
    else: 
        reverse_templates[value].append(key)

Here is what I tried in ansible, which doesn't work :

- name: "Associate each template with related VMs list"
  set_fact:
    reverse_templates: "{{reverse_templates|default({})|combine({item.value:{% if item.value not in reverse_teplates %}value[item.key]{% else %}reverse_templates[item.value] + [item.key]{% endif %}})}}"
  loop: "{{lookup('dict', vm_templates)}}"

Here is the error :

TASK [Associate each template with related VMs list] ******************************************************************************************************************************************************* fatal: [localhost]: FAILED! => {"msg": "template error while templating string: unexpected '%'. String: {{reverse_templates|default({})|combine({item.value:{% if item.value not in reverse_teplates %}value[item.key]{% else %}reverse_templates[item.value] + [item.key]{% endif %}})}}"}

Any good approach to get there using Ansible?

1 Answers

The task below does the job

    - set_fact:
        reverse_templates: "{{ reverse_templates|default([]) +
                               [{item: vm_templates|
                                       dict2items|
                                       selectattr('value', 'eq', item)|
                                       map(attribute='key')|
                                       list}] }}"
      loop: "{{ vm_templates.values()|unique|list }}"
    - debug:
        var: reverse_templates

give

  reverse_templates:
  - a08901c0-50a5-4e2f-b6d7-b11b69a32613:
    - fp_gpu-node0
    - fp_gpu-node1
    - fp_gpu-node10
    - fp_gpu-node11
    - fp_gpu-node2
    - fp_gpu-node3
    - fp_gpu-node4
    - fp_gpu-node5
    - fp_gpu-node6
    - fp_gpu-node7
    - fp_gpu-node8
    - fp_gpu-node9
    - hp_gpu-node0
    - hp_gpu-node1
    - hp_gpu-node2
    - hp_gpu-node3
    - hp_gpu-node4
    - hp_gpu-node5
    - hp_gpu-node6
    - hp_gpu-node7
  - 00000000-0000-0000-0000-000000000000:
    - HostedEngine
    - infra-vm
  - 2d826ed8-1dbe-4b10-93a1-5ac9734462cb:
    - cpu-node0
    - cpu-node1
    - cpu-node2
    - cpu-node3

The Ansible code is similar to Python

for key in set(vm_templates.values()):
    reverse_templates[key] = [k for k,v in vm_templates.items() if v == key]
print(reverse_templates)

json_query

The task below produces the same result

    - set_fact:
        reverse_templates: "{{ reverse_templates|default([]) +
                               [{item: vm_templates|
                                       dict2items|
                                       json_query(query)}] }}"
      loop: "{{ vm_templates.values()|unique|list }}"
      vars:
        query: "[?value == '{{ item }}'].key"
Related