Ansible: How to get a subelement of a nested list of dictionaries

9,802

Figured out how to use json_query properly.

  - debug:                                                                      
      msg: "{{ dns_zone_config | json_query(_query) }}"                         
    vars:                                                                       
      _query: "[].hosts[?!no_ptr_record][].[ip,ipv6][]"    

Results in:

TASK [debug] ***********************************************************************************
ok: [localhost] => {
    "msg": [
        "10.180.0.200",
        "fe80::1",
        "11.180.0.200"
    ]
}

Needs just 1-2 sec instead of ~30 sec with double loops :)

Share:
9,802

Related videos on Youtube

Mario Nette
Author by

Mario Nette

Updated on September 18, 2022

Comments

  • Mario Nette
    Mario Nette over 1 year

    I'd like to build our DNS zones via Ansible. The user should just maintain one variable for the forward-lookup zones (e.g. foo.bar). Reverse-lookup zones (0.0.10.in-appr.arpa) should be auto-generated with Ansible.

    The forward-lookup zone variable should look like

    dns_zone_config:
        - name: "internal.foo.bar"
          acl:
            - 10.180.0.0/24
            - 10.180.8.0/24
          hosts:
            - name: "fileshare"
              ip: 10.180.0.200
        - name: "infra.foo.bar"
          acl:
            - acl-intern
          hosts:
            - name: "testhost1"
              ip: 11.180.0.100
              no_ptr_record: true
            - name: "testhost2"
              ipv6: fe80::1
            - name: "testhost3"
              ip: 11.180.0.200
        - name: "mx.foo.bar"
          mx:
            - name: "xxxx"
              priority: xxxx
              target: xxxx
    

    dns_zone_config is a list of dictionaries that may contains a key called "hosts" which is again a list of dictionaries.

    My current way looks like:

    - name: Collect all networks
      include_tasks: 01-networks.yml
      loop: "{{ dns_zone_config }}"
      when: item_dns_zone_config.hosts is defined
      loop_control:
        loop_var: item_dns_zone_config
    
    #from 01-networks.yml
    - name: Determine IPv4 networks
      set_fact:
        ipv4_networks: "{{ (ipv4_networks | default([])) + [ item.ip ] }}"
      loop: "{{ item_dns_zone_config.hosts }}"
      when: item.ip is defined and (item.no_ptr_record is not defined or not item.no_ptr_record)
    

    I double loop over dns_zone_config and their host entries. This is very inefficient and slow. I'm pretty sure this can be solve smarter :D

    Basically I just need list of all IPs of all zones. I tried json_query() and selectattr() filters but I'm struggling on the fact that not every host entry has IPv4 or IPv6 addresses defined. And I don't want contains IPs if a no_ptr_record: true variable is defined (false or not defined is ok).

    From the snippet above the list would only contain: ipv4_networks:

    ['10.180.0.200','11.180.0.200']
    
  • simohe
    simohe over 3 years
    Could you please explain the query? It looks great. I do not find ? !xx on jmespath.org/tutorial.html#filter-projections (but I probably have found it out now by trying there).
  • Mario Nette
    Mario Nette over 3 years
    With ? you start a condition check. With ! you can negate a boolean. So [?!no_ptr_record] means take all elements where no_ptr_record is not true. If a list element has not no_ptr_record defined it will be also be handled as false (with negation it will be true). See jmespath.org/specification.html