Merging Hash of Lists in Ansible

ansible

I'm trying to set /etc/hosts entries in a playbook.

I'm managing three hashes of lists: common_stubs, env_stubs, and host_stubs.

example of what I'm working with here

common_stubs:
  "127.0.0.1": 
    - localhost

env_stubs:
  "127.0.0.1": 
    - someservice.mydomain

host_stubs: {}

I want to combine these so I get

combined_stubs:
  "127.0.0.1":
    - localhost
    - someservice.mydomain

This is what I'm currently doing

- name: "Configure /etc/hosts"
  lineinfile:
    path: /etc/hosts
    regex: "{{ '^' + item.key + '.*$' }}"
    line: "{{ item.key + ' ' + ' '.join(item.value) }}"
    state: "{% if item.value | length > 0 %}present{% else %}absent{% endif %}"
  loop: "{{ common_stubs | combine(env_stubs, host_stubs, recursive=True) | dict2items }}"
  become: true

but the array in env_stubs is overriding the array in common_stubs

Best Answer

Let's create the list of IPs first

- set_fact:
    list_ip: "{{ list_ip|default([]) + item }}"
  loop:
    - "{{ common_stubs.keys()|list }}"
    - "{{ env_stubs.keys()|list }}"
    - "{{ host_stubs.keys()|list }}"
- set_fact:
    list_ip: "{{ list_ip|flatten|unique }}"

then loop the list and combine the dictionary

- set_fact:
    combined_stubs: "{{ combined_stubs|default({})|
                        combine({item: [[common_stubs[item]|default([])] +
                                        [env_stubs[item]|default([])] +
                                        [host_stubs[item]|default([])]]|
                                       flatten}) }}"
  loop: "{{ list_ip }}"

give

"combined_stubs": {
    "127.0.0.1": [
        "localhost", 
        "someservice.mydomain"
    ]
}


Next option is to create custom filter

$ cat filter_plugins/dict_utils.py
def dict_merge_lossless(x, y):
    d = x.copy()
    d.update(y)
    for key, value in d.items():
       if key in x and key in y:
               d[key] = [value , x[key]]
    return d

class FilterModule(object):
    ''' Ansible filters. Interface to Python dictionary methods.'''

    def filters(self):
        return {
            'dict_merge_lossless' : dict_merge_lossless
        }

Then the tasks below

- set_fact:
    combined_stubs: "{{ common_stubs|
                        dict_merge_lossless(env_stubs)|
                        dict_merge_lossless(host_stubs) }}"
- debug:
    var: combined_stubs

give

  combined_stubs:
    127.0.0.1:
    - - someservice.mydomain
    - - localhost