Home > Net >  merge dictionaries in Ansible that have the same keys
merge dictionaries in Ansible that have the same keys

Time:08-05

once again I'm trying to accomplish something with Ansible.

I built a custom dict with variables for each server that looks like this.

- name: Create and Add items to server_list
  set_fact: 
    server_list: "{{ server_list | default({}) | combine ({ item.key : item.value }) }}"
  with_items:
    - { 'key': 'Servername' , 'value': "{{ ansible_hostname }}"}
    - { 'key': 'IP-Adresse' , 'value': "{{ ansible_default_ipv4.address }}"}
    - { 'key': 'OS' , 'value': "{{ ansible_os_family }} {{ ansible_distribution_major_version }}"}
    - { 'key': 'Plattform' , 'value': "{{ ansible_system }}"}

- name: DEBUG server_list
  debug:
    var=server_list

That's working. But now ansible outputs this for every server like this:

ok: [XXX] => {
    "server_list": {
        "IP-Adresse": "x.x.x.x",
        "OS": "Debian 11",
        "Plattform": "Linux",
        "Servername": "XXX"
    }
}
ok: [YYY] => {
    "server_list": {
        "IP-Adresse": "x.x.x.y",
        "OS": "Debian 11",
        "Plattform": "Linux",
        "Servername": "YYY"
    }
}

...

What I want to have now is a complete and "global" dict with every server. I think that would look like this. Example of what I want as output (here named as server_list_dict):

{
    "server_list_dict": {
        "0": {
            "Servername": "XXX",
            "IP-Adresse": "x.x.x.x",
            "OS": "Debian 11",
            "Plattform": "Linux"
        },
        "1": {
            "Servername": "YYY",
            "IP-Adresse": "x.x.x.y",
            "OS": "Debian 11",
            "Plattform": "Linux"
        }
    }
}

Only for clarification for what I want to achieve

What I want to do later is create a html table that should output something like this. Example html that I want to create:

<table style="width:50%">
    <!-- table header -->
    <tr>
        <th>Servername</th>
        <th>IP</th>
        <th>OS</th>
        <th>Platform</th>
    </tr>  
    <!-- table rows -->
    <tr>
        <td>Hostname1</td>
        <td>X.X.X.X</td>
        <td>Debian</td>
        <td>Linux</td>
    </tr>
    <tr>
        <td>Hostname2</td>
        <td>X.X.X.Y</td>
        <td>Debian</td>
        <td>Linux</td>
    </tr>
</table>

I already have a template to use. This will go through my "global" dict with every server and use all of the keys and values that are in there. (hopefully, couldnt test it, because idk how to merge my dicts)

<table style="width:100%">
  <!-- table header -->
  {% if server_list_dict %}
  <tr>
     {% for key in server_list_dict[0] %}
     <th> {{ key }} </th>
     {% endfor %}
  </tr>
  {% endif %}

  <!-- table rows -->
  {% for dict_item in server_list_dict %}
  <tr>
     {% for value in dict_item.values() %}
     <td> {{ value }} </td>
     {% endfor %}
  </tr>
  {% endfor %}
</table>

Does anybody know how to merge dicts with the same keys in a complete dict? I couldn't find anything useful. Thanks everyone in advance!

CodePudding user response:

Extract the list of the variables server_list

server_lsts: "{{ ansible_play_hosts|
                 map('extract', hostvars, 'server_list')|list }}"

gives

server_lsts:
  - IP-Adresse: x.x.x.x
    OS: Debian 11
    Plattform: Linux
    Servername: XXX
  - IP-Adresse: x.x.x.y
    OS: Debian 11
    Plattform: Linux
    Servername: YYY

and create the dictionary

server_dict: "{{ dict(ansible_play_hosts|zip(server_lsts)) }}"

gives

server_dict:
  XXX:
    IP-Adresse: x.x.x.x
    OS: Debian 11
    Plattform: Linux
    Servername: XXX
  YYY:
    IP-Adresse: x.x.x.y
    OS: Debian 11
    Plattform: Linux
    Servername: YYY

  • Example of a complete playbook
- hosts: all
  vars:
    server_lsts: "{{ ansible_play_hosts|
                     map('extract', hostvars, 'server_list')|list }}"
    server_dict: "{{ dict(ansible_play_hosts|zip(server_lsts)) }}"
  tasks:
    - debug:
        var: server_dict
      run_once: true
  • Put the declaration of the dictionary into the group_vars. For example
shell> cat group_vars/all.yml
server_list:
  Servername: "{{ ansible_hostname }}"
  IP-Adresses: "{{ ansible_all_ipv4_addresses }}"
  OS: "{{ ansible_os_family }} {{ ansible_distribution_major_version }}"
  Plattform: "{{ ansible_system }}"

The playbook

- hosts: test_11:test_12
  vars:
    server_lsts: "{{ ansible_play_hosts|
                     map('extract', hostvars, 'server_list')|list }}"
    server_dict: "{{ dict(ansible_play_hosts|zip(server_lsts)) }}"
  tasks:
    - debug:
        var: server_dict
      run_once: true

gives

server_dict:
  test_11:
    IP-Adresses:
      - 10.1.0.61
    OS: FreeBSD 13
    Plattform: FreeBSD
    Servername: test_11
  test_12:
    IP-Adresses:
      - 10.1.0.62
    OS: FreeBSD 13
    Plattform: FreeBSD
    Servername: test_12
  • If you want to index the dictionaries create the sequence. For example
shell> cat pb.yml
- hosts: test_11:test_12
  vars:
    server_lsts: "{{ ansible_play_hosts|
                     map('extract', hostvars, 'server_list')|list }}"
    params: "start=0 count={{ ansible_play_hosts|length }}"
    server_dict: "{{ dict(q('sequence', params)|zip(server_lsts)) }}"
  tasks:
    - debug:
        var: server_dict
      run_once: true

gives

server_dict:
  '0':
    IP-Adresses:
      - 10.1.0.61
    OS: FreeBSD 13
    Plattform: FreeBSD
    Servername: test_11
  '1':
    IP-Adresses:
      - 10.1.0.62
    OS: FreeBSD 13
    Plattform: FreeBSD
    Servername: test_12

CodePudding user response:

you could directly use jinja to create your var as expected:

  tasks:
    - name: Create and Add items to server_list
      set_fact: 
        server_list: >- 
              {%- set result = {} -%}
              {%- for server in ansible_play_hosts -%}
              {%- set idx = loop.index0 -%}
              {%- set name = hostvars[server].ansible_hostname -%}
              {%- set ip = hostvars[server].ansible_default_ipv4.address -%}
              {%- set os = hostvars[server].ansible_os_family -%}
              {%- set pf = hostvars[server].ansible_system -%}
              {%- set _= result.update({idx: {"Servername": name, "IP": ip, "OS": os, "Platform": pf} }) -%}
              {%- endfor -%}
              {{ result }}
      
    - debug:
        msg: "{{server_list}}" 

CodePudding user response:

... Ansible outputs this for every server ... I have mutliple dicts now (each for every server because of the set_fact) ...

This is since gather_facts module – Gathers facts about remote hosts and set_fact module – Set host variable(s) and fact(s) are running distributed on the Remote Node(s) and the expected behavior.

What I want to have now is a complete and "global" dict with every server.

I understand your question that you like to delegate facts first to one of the hosts, the Control Node (Deployment Host or localhost) where you can than create your file or use your template for further distribution. There, all the information would be aggregated in one data structure (dictionary) already.

An other approach might be to use caching facts via cache plugins.

Documentation

  • Related