Kris Reese
Kris Reese

Reputation: 111

Use json_query in Ansible to query a list of dictionaries contained within a list of dictionaries

I am having trouble using json_query to search the below data structure from which I want to create two separate lists.

List 1 should contain the name key from each dictionary for entries whose members list contains the subPath key AND whose value contains a specific value.

List 2 should contain the name key from each dictionary for entries whose members list does NOT contain the subPath key and whose name contains a specific value.

The closest I'm able to get thus far is finding those dictionaries whose members dictionary has the subPath key:

- name: debug
  debug:
    msg: "{{ device_facts.gtm_a_pools | json_query(query) }}"
  vars:
    query: "[].members[?!subPath][].name"

or doesn't have the subPath key:

- name: debug
  debug:
    msg: "{{ device_facts.gtm_a_pools | json_query(query) }}"
  vars:
    query: "[].members[?subPath][].name"

But this is returning the name key from within the members dictionary, when what I want is the name key from each gtm_a_pools dictionary based on the two above criterion (one list that has the subPath and one list for those without).

Using the below data structure:

    "gtm_a_pools": [
        {
            "alternate_mode": "global-availability",
            "dynamic_ratio": "no",
            "enabled": "yes",
            "fallback_mode": "return-to-dns",
            "full_path": "/Common/a2-test_pool",
            "load_balancing_mode": "global-availability",
            "manual_resume": "no",
            "max_answers_returned": 1,
            "members": [
                {
                    "disabled": "no",
                    "enabled": "yes",
                    "limitMaxBps": 0,
                    "limitMaxBpsStatus": "disabled",
                    "limitMaxConnections": 0,
                    "limitMaxConnectionsStatus": "disabled",
                    "limitMaxPps": 0,
                    "limitMaxPpsStatus": "disabled",
                    "member_order": 0,
                    "monitor": "default",
                    "name": "a2-test_443_vs",
                    "partition": "Common",
                    "ratio": 1,
                    "subPath": "test-dmz-dc1-pair:/Common"
                },
                {
                    "disabled": "no",
                    "enabled": "yes",
                    "limitMaxBps": 0,
                    "limitMaxBpsStatus": "disabled",
                    "limitMaxConnections": 0,
                    "limitMaxConnectionsStatus": "disabled",
                    "limitMaxPps": 0,
                    "limitMaxPpsStatus": "disabled",
                    "member_order": 1,
                    "monitor": "default",
                    "name": "dc2-a2-test_443_vs",
                    "partition": "Common",
                    "ratio": 1,
                    "subPath": "test-dmz-dc2-pair:/Common"
                }
            ],
            "name": "a2-test_pool",
            "partition": "Common",
            "qos_hit_ratio": 5,
            "qos_hops": 0,
            "qos_kilobytes_second": 3,
            "qos_lcs": 30,
            "qos_packet_rate": 1,
            "qos_rtt": 50,
            "qos_topology": 0,
            "qos_vs_capacity": 0,
            "qos_vs_score": 0,
            "ttl": 30,
            "verify_member_availability": "yes"
        },
        {
            "alternate_mode": "round-robin",
            "dynamic_ratio": "no",
            "enabled": "yes",
            "fallback_mode": "return-to-dns",
            "full_path": "/Common/aci_pool",
            "load_balancing_mode": "round-robin",
            "manual_resume": "no",
            "max_answers_returned": 1,
            "members": [
                {
                    "disabled": "no",
                    "enabled": "yes",
                    "limitMaxBps": 0,
                    "limitMaxBpsStatus": "disabled",
                    "limitMaxConnections": 0,
                    "limitMaxConnectionsStatus": "disabled",
                    "limitMaxPps": 0,
                    "limitMaxPpsStatus": "disabled",
                    "member_order": 0,
                    "monitor": "default",
                    "name": "prod_dc1_servers:aci",
                    "partition": "Common",
                    "ratio": 1
                },
                {
                    "disabled": "no",
                    "enabled": "yes",
                    "limitMaxBps": 0,
                    "limitMaxBpsStatus": "disabled",
                    "limitMaxConnections": 0,
                    "limitMaxConnectionsStatus": "disabled",
                    "limitMaxPps": 0,
                    "limitMaxPpsStatus": "disabled",
                    "member_order": 1,
                    "monitor": "default",
                    "name": "prod_dc1_servers:aci",
                    "partition": "Common",
                    "ratio": 1
                }
            ],
            "name": "aci_pool",
            "partition": "Common",
            "qos_hit_ratio": 5,
            "qos_hops": 0,
            "qos_kilobytes_second": 3,
            "qos_lcs": 30,
            "qos_packet_rate": 1,
            "qos_rtt": 50,
            "qos_topology": 0,
            "qos_vs_capacity": 0,
            "qos_vs_score": 0,
            "ttl": 30,
            "verify_member_availability": "yes"
        }
    ]

Upvotes: 1

Views: 1293

Answers (2)

Kris Reese
Kris Reese

Reputation: 111

Thank you, Vladimir!

While I was awaiting a response from someone, I came up with this so I thought I'd share it here. I will say that this may not necessarily account for both scenarios for list 2 whereby I account for "all members without subPath" vs "any member without subPath". Because of that, I used your solution above to run a validation at the end to process the entire load and draw that comparison, and output any pool that has a mix of member types:

---
- name: Determine if Server List product is Generic Host (static) or otherwise
  bigip_device_info:
    gather_subset:
      - gtm-servers
    provider: "{{ vcmp_guest_provider }}"
  delegate_to: localhost
  register: device_facts

# server_type = generic-host vs. bigip
# bigip = dynamic
# generic-host = static
- name: Set type fact
  set_fact:
    server_type: "{{ device_facts | json_query(jmesquery) | join }}"
  vars:
    jmesquery: "gtm_servers[?name == '{{ gtm_source_server }}'].product"

- name: debug
  debug:
    var: server_type

- name: Gather GTM pools 
  bigip_device_info:
    gather_subset:
      - gtm-a-pools
    provider: "{{ vcmp_guest_provider }}"
  delegate_to: localhost
  register: device_facts

- name: Create list of pools that use discovered objects and are part of {{ gtm_source_server }}
  set_fact:
    pool_list: "{{ pool_list | default([]) + [ item.0 ] }}"
  loop: "{{ pool_name | zip(payload) | list }}"
  when:
    - item.1 | json_query('members[*].subPath') | length > 0
    - item.1 | json_query('members[*].subPath') is search(gtm_source_server)
    - server_type == "bigip"
  vars:
    pool_name: "{{ device_facts | json_query('gtm_a_pools[*].name') }}"
    payload: "{{ device_facts | json_query('gtm_a_pools[*]') }}"
  no_log: true

- name: Create list of pools that use static objects and are part of {{ gtm_source_server }}
  set_fact:
    pool_list: "{{ pool_list | default([]) + [ item.0 ] }}"
  loop: "{{ pool_name | zip(payload) | list }}"
  when:
    - item.1 | json_query('members[*].subPath') | length == 0
    - item.1 | json_query('members[*].name') is search(gtm_source_server)
    - server_type == "generic-host"
  vars:
    pool_name: "{{ device_facts | json_query('gtm_a_pools[*].name') }}"
    payload: "{{ device_facts | json_query('gtm_a_pools[*]') }}"
  no_log: true

- debug:
    var: pool_list

- name: Check if any pools contain a mix of static and dynamic members
  set_fact:
    counter: "{{ counter | default([]) + [ item.name + ' has ' + subPath_length + ' dynamic entries and ' + member_length + ' static entries.' ] }}"
  loop: "{{ device_facts | json_query('gtm_a_pools[*]') }}"
  vars:
    subPath_length: "{{ item.members|map('intersect', ['subPath'])|flatten|length }}"
    member_length: "{{ item.members|length }}"
  when:
    - subPath_length | int > 0
    - member_length != subPath_length
  no_log: true

- debug:
    msg: "{{ counter | default('No mixture found') }}"

Upvotes: 0

Vladimir Botka
Vladimir Botka

Reputation: 68189

Q: "Name key from each gtm_a_pools dictionary based on the two above criterion (one list that has the subPath and one list for those without)"

A: Instead of json_query, iterate the list and compare the number of members e.g.

    - set_fact:
        list1: "{{ list1|default([]) + [item.name] }}"
      loop: "{{ gtm_a_pools }}"
      when: item_length == subPath_length
      vars:
        subPath_length: "{{ item.members|map('intersect', ['subPath'])|flatten|length }}"
        item_length: "{{ item.members|length }}"

gives

  list1:
  - a2-test_pool

If "without subPath" means "any member without subPath"

    - set_fact:
        list2: "{{ list2|default([]) + [item.name] }}"
      loop: "{{ gtm_a_pools }}"
      when: item_length != subPath_length
      vars:
        subPath_length: "{{ item.members|map('intersect', ['subPath'])|flatten|length }}"
        item_length: "{{ item.members|length }}"

gives

  list2:
  - aci_pool

If "without subPath" means "all members without subPath" the task below gives the same result

    - set_fact:
        list2: "{{ list2|default([]) + [item.name] }}"
      loop: "{{ gtm_a_pools }}"
      when: subPath_length|int == 0
      vars:
        subPath_length: "{{ item.members|map('intersect', ['subPath'])|flatten|length }}"

Upvotes: 1

Related Questions