Home > front end >  Ansible - Combining Lists of Dictionaries
Ansible - Combining Lists of Dictionaries

Time:01-31

I have two lists of dictionaries. The first list of dict contains detailed information for all systems in an environment.

"cluster_detail": [
    {
        "env_name": "env_1",
        "cluster_name": "cluster_1",
        "primary": "server1",
        "secondary": "server2,server3",
        "Standby": "server4,server5",
        "TieBreakers": "server6,server7"
    },
    {
        "env_name": "env_1",
        "cluster_name": "cluster_2",
        "primary": "server11",
        "secondary": "server12,server13",
        "Standby": "server14,server15",
        "TieBreakers": "server16,server17"
    },
    {
        "env_name": "env_2",
        "cluster_name": "cluster_7",
        "primary": "server21",
        "secondary": "server22,server23",
        "Standby": "server24,server25",
        "TieBreakers": "server26,server27"
    },
    {
        "env_name": "env_2",
        "cluster_name": "cluster_8",
        "primary": "server31",
        "secondary": "server32,server33",
        "Standby": "server34,server35",
        "TieBreakers": "server36,server37"
    }
]

The second list of dict contains partial detail for some parts of the same environment.

"cluster_partial": [
    {
        "key": "env_1",
        "value": [
            "cluster_1",
            "cluster_2",
            "cluster_3",
            "cluster_4"
        ]
    },
    {
        "key": "env_2",
        "value": [
            "cluster_5",
            "cluster_6",
            "cluster_7",
            "cluster_8"
        ]
    }
]

I would like to add detail to "cluster_partial" by searching for its values (cluster_1, Cluster_2, etc...) in "Cluster_detail", and where there's a match, add in the standby or tiebreaker server names.

I've been going through https://docs.ansible.com/ansible/latest/playbook_guide/complex_data_manipulation.html and attempting various combinations of the examples given but am not having any success.

I've also looked at ansible - combine three lists of dictionaries but need to be more selective I think.

What I would like to end up with is

"end_result": [
    {
        "key": "env_1",
        "value": [
            "cluster_1":
                tiebreaker:[server6,server7],
            "cluster_2":
                tiebreaker:[server16,server17],
            "cluster_3",
            "cluster_4"
        ]
    },
    {
        "key": "env_2",
        "value": [
            "cluster_5",
            "cluster_6",
            "cluster_7":
                tiebreaker:[server26,server27],
            "cluster_8:
                tiebreaker:[server36,server37]"
        ]
    }
]

I've tried the following

- name: Add tiebreaker Value to list of Dictionary  
  ansible.builtin.set_fact:
    cluster_partial: |
        {% for item in cluster_partialm %}
        "tiebreaker": {{item}}
        {% endfor %}
    when: cluster_detail.cluster_name is defined and cluster_detail.cluster_name == cluster_partial.value`

CodePudding user response:

It looks like cluster_partial is unnecessary for what you're doing; we can achieve something that almost matches your desired end state by using only the information in cluster_detail:

- hosts: localhost
  gather_facts: false
  tasks:
    - set_fact:
        end_result: >-
          {{
            end_result|combine({
              item.env_name: {
                item.cluster_name: {
                  "tiebreaker": item.TieBreakers.split(',')
                }
              }
            }, recursive=True)
          }}
      vars:
        end_result: {}
      loop: "{{ cluster_detail }}"

    - debug:
        var: end_result

Which produces:

TASK [debug] *******************************************************************
ok: [localhost] => {
    "end_result": {
        "env_1": {
            "cluster_1": {
                "tiebreaker": [
                    "server6",
                    "server7"
                ]
            },
            "cluster_2": {
                "tiebreaker": [
                    "server16",
                    "server17"
                ]
            }
        },
        "env_2": {
            "cluster_7": {
                "tiebreaker": [
                    "server26",
                    "server27"
                ]
            },
            "cluster_8": {
                "tiebreaker": [
                    "server36",
                    "server37"
                ]
            }
        }
    }
}

CodePudding user response:

Create the dictionary of available clusters

  cluster_name_tiebreaker: "{{ cluster_detail|
                               items2dict(key_name='cluster_name',
                                          value_name='TieBreakers') }}"

gives

  cluster_name_tiebreaker:
    cluster_1: server6,server7
    cluster_2: server16,server17
    cluster_7: server26,server27
    cluster_8: server36,server37

Use Jinja to create the structure

  end_result_str: |
    {% for e in cluster_partial %}
    {{ e.key }}:
    {% for cluster in e.value %}
    {% if cluster in cluster_name_tiebreaker %}
      {{ cluster }}:
        tiebreaker: {{ cluster_name_tiebreaker[cluster]|split(',') }}
    {% else %}
      {{ cluster }}:
    {% endif %}
    {% endfor %}
    {% endfor %}
  end_result_dict: "{{ end_result_str|from_yaml }}"

gives the dictionary

  end_result_dict:
    env_1:
      cluster_1:
        tiebreaker: [server6, server7]
      cluster_2:
        tiebreaker: [server16, server17]
      cluster_3: null
      cluster_4: null
    env_2:
      cluster_5: null
      cluster_6: null
      cluster_7:
        tiebreaker: [server26, server27]
      cluster_8:
        tiebreaker: [server36, server37]

Convert the dictionary to the list

  end_result: "{{ end_result_dict|dict2items }}"

gives

  end_result:
    - key: env_1
      value:
        cluster_1:
          tiebreaker: [server6, server7]
        cluster_2:
          tiebreaker: [server16, server17]
        cluster_3: null
        cluster_4: null
    - key: env_2
      value:
        cluster_5: null
        cluster_6: null
        cluster_7:
          tiebreaker: [server26, server27]
        cluster_8:
          tiebreaker: [server36, server37]

This is not exactly what you want. The attribute value is a dictionary. If you really want a list change the template

    end_result_str: |
      {% for e in cluster_partial %}
      {{ e.key }}:
      {% for cluster in e.value %}
      {% if cluster in cluster_name_tiebreaker %}
        - {{ cluster }}:
            tiebreaker: {{ cluster_name_tiebreaker[cluster]|split(',') }}
      {% else %}
        - {{ cluster }}:
      {% endif %}
      {% endfor %}
      {% endfor %}

gives

  end_result:
    - key: env_1
      value:
      - cluster_1:
          tiebreaker: [server6, server7]
      - cluster_2:
          tiebreaker: [server16, server17]
      - {cluster_3: null}
      - {cluster_4: null}
    - key: env_2
      value:
      - {cluster_5: null}
      - {cluster_6: null}
      - cluster_7:
          tiebreaker: [server26, server27]
      - cluster_8:
          tiebreaker: [server36, server37]

Fit the template further if you really want to have various types of items in the lists. Remove the colon : to get strings instead of dictionaries with undefined (null) values

      {% else %}
        - {{ cluster }}

gives what you want

  end_result:
    - key: env_1
      value:
      - cluster_1:
          tiebreaker: [server6, server7]
      - cluster_2:
          tiebreaker: [server16, server17]
      - cluster_3
      - cluster_4
    - key: env_2
      value:
      - cluster_5
      - cluster_6
      - cluster_7:
          tiebreaker: [server26, server27]
      - cluster_8:
          tiebreaker: [server36, server37]

Example of a complete playbook for testing

- hosts: localhost
  
  vars:

    cluster_detail:
      - Standby: server4,server5
        TieBreakers: server6,server7
        cluster_name: cluster_1
        env_name: env_1
        primary: server1
        secondary: server2,server3
      - Standby: server14,server15
        TieBreakers: server16,server17
        cluster_name: cluster_2
        env_name: env_1
        primary: server11
        secondary: server12,server13
      - Standby: server24,server25
        TieBreakers: server26,server27
        cluster_name: cluster_7
        env_name: env_2
        primary: server21
        secondary: server22,server23
      - Standby: server34,server35
        TieBreakers: server36,server37
        cluster_name: cluster_8
        env_name: env_2
        primary: server31
        secondary: server32,server33

    cluster_partial:
      - key: env_1
        value: [cluster_1, cluster_2, cluster_3, cluster_4]
      - key: env_2
        value: [cluster_5, cluster_6, cluster_7, cluster_8]

    cluster_name_tiebreaker: "{{ cluster_detail|
                                 items2dict(key_name='cluster_name',
                                            value_name='TieBreakers') }}"
    end_result_str: |
      {% for e in cluster_partial %}
      {{ e.key }}:
      {% for cluster in e.value %}
      {% if cluster in cluster_name_tiebreaker %}
        - {{ cluster }}:
            tiebreaker: {{ cluster_name_tiebreaker[cluster]|split(',') }}
      {% else %}
        - {{ cluster }}
      {% endif %}
      {% endfor %}
      {% endfor %}
    end_result_dict: "{{ end_result_str|from_yaml }}"
    end_result: "{{ end_result_dict|dict2items }}"

  tasks:

    - debug:
        var: cluster_name_tiebreaker
    - debug:
        var: end_result_str
    - debug:
        var: end_result_dict|to_yaml
    - debug:
        var: end_result|to_yaml
  • Related