Search code examples
automationansiblejinja2

How to create list from dictionary key-value pairs in Ansible?


I'm using Ansible (v2.14.3) and I'm trying to process ansible_facts to get data about active Docker network interfaces. I need to get 3 fields and store them in a variable: device, network, prefix. After getting those 3 fields, I need to concat network and prefix to get a valid CIDR block, but I just don't understand, how I can make it happen.

My steps:

---
- hosts: all
  tasks:
    - name: Get network interface names.
      set_fact:
        _device: "{{ ansible_facts | dict2items | selectattr('value.ipv4', 'defined') | selectattr('value.active', 'true') | map(attribute='value') | map(attribute='device') | list }}"


    - name: Get IPv4 params of network interfaces.
      set_fact:
        _ipv4: "{{ ansible_facts | dict2items | selectattr('value.ipv4', 'defined') | selectattr('value.active', 'true') | map(attribute='value') | map(attribute='ipv4') | list }}"


    - name: Get network address from IPv4 params.
      set_fact:
        _network: "{{ _ipv4 | map(attribute='network')}}"

    - name: Get network prefix from IPv4 params.
      set_fact:
        _prefix: "{{ _ipv4 | map(attribute='prefix')}}"

    - name: Concat network addresses and prefixes into a dictionary.
      set_fact:
        _dict: "{{ dict( _network | zip(_prefix )) | dict2items }}"

    - name: Get network CIDR blocks.
      set_fact:
        _cidrs: "{{_values | default([]) + '[item.key/item.value]'}}"
      loop: "{{ _dict }}"

Alternative version of the last step:

- name: Get network CIDR blocks.
      vars:
        _cidrs: []
      set_fact: 
        _cidrs: "{{ _cidrs }} + '[{{ item.key }}/{{ item.value }}]'"
      loop: "{{ _dict }}"

Both variants fail, or I end up with AnsibleUnsafeText date type, which is hard to work with...

Output of the script, provided above is the following:

TASK [Debug network interface names.] *************************************
ok: [server] => {
    "_device": [
        "br-624acf7a2c6d",
        "lo",
        "eth0"
    ]
}

TASK [Debug IPv4 params of network interfaces.] ************************************************
ok: [server] => {
    "_ipv4": [
        {
            "address": "172.24.0.1",
            "broadcast": "172.24.255.255",
            "netmask": "255.255.0.0",
            "network": "172.24.0.0",
            "prefix": "16"
        },
        {
            "address": "127.0.0.1",
            "broadcast": "",
            "netmask": "255.0.0.0",
            "network": "127.0.0.0",
            "prefix": "8"
        },
        {
            "address": "192.168.0.2",
            "broadcast": "192.168.0.255",
            "netmask": "255.255.255.0",
            "network": "192.168.0.0",
            "prefix": "24"
        }
    ]
}

TASK [Debug network address from IPv4 params.] ********************************************
ok: [server] => {
    "_network": [
        "172.24.0.0",
        "127.0.0.0",
        "192.168.0.0"
    ]
}

TASK [Debug network prefix from IPv4 params.] *****************************************
ok: [server] => {
    "_prefix": [
        "16",
        "8",
        "24"
    ]
}

TASK [Debug dict.] ***********
ok: [server] => {
    "_dict": [
        {
            "key": "172.24.0.0",
            "value": "16"
        },
        {
            "key": "127.0.0.0",
            "value": "8"
        },
        {
            "key": "192.168.0.0",
            "value": "24"
        }
    ]
}

TASK [Debug CIDRs.] *********************
ok: [server] => {
    "_cidrs": "[] + '[172.24.0.0/16]' + '[127.0.0.0/8]' + '[192.168.0.0/24]'"
}

Solution

  • Short answer: Use json_query function join

      result: "{{ ansible_facts|dict2items|
                  selectattr('value.ipv4', 'defined')|
                  selectattr('value.active')|
                  map(attribute='value.ipv4')|
                  json_query('[].join(`/`, [network, prefix])') }}"
    

    gives the list of CIDR. For example,

      result:
      - 10.1.0.0/24
      - 127.0.0.0/8
    

    Details: Store the list of active devices so you don't have to repeat this selection

      devs: "{{ ansible_facts|dict2items|
                selectattr('value.ipv4', 'defined')|
                selectattr('value.active')|
                map(attribute='value') }}"
    

    Then, start selecting what you want. The list of the devices

      _device: "{{ devs|map(attribute='device') }}"
    

    e.g.

      _device:
      - eth1
      - lo
    

    , the IPv4 attributes of the devices

      _ipv4: "{{ devs|map(attribute='ipv4') }}"
    

    e.g.

      _ipv4:
      - address: 10.1.0.184
        broadcast: 10.1.0.255
        netmask: 255.255.255.0
        network: 10.1.0.0
        prefix: '24'
      - address: 127.0.0.1
        broadcast: ''
        netmask: 255.0.0.0
        network: 127.0.0.0
        prefix: '8'
    

    , the dictionary of the network and prefix

      _dict: "{{ _ipv4|items2dict(key_name='network', value_name='prefix') }}"
    

    .e.g.

      _dict:
        10.1.0.0: '24'
        127.0.0.0: '8'
    

    , and the list of the same (this is what you call _dict in your example)

      _list: "{{ _dict|dict2items }}"
    

    e.g.

      _list:
      - key: 10.1.0.0
        value: '24'
      - key: 127.0.0.0
        value: '8'
    

    Get the list of CIDR by zipping the keys and values of the dictionary and joining the items

      _cidrs: "{{ _dict.keys()|zip(_dict.values())|map('join', '/')|list }}"
    
    

    e.g.

      _cidrs:
      - 10.1.0.0/24
      - 127.0.0.0/8
    

    You get the same result by joining the attributes of the list in json_query

      _cidrs: "{{ _list|json_query('[].join(`/`, [key, value])') }}"
    

    Example of a complete playbook for testing

    - hosts: localhost
    
      vars:
    
        result: "{{ ansible_facts|dict2items|
                    selectattr('value.ipv4', 'defined')|
                    selectattr('value.active')|
                    map(attribute='value.ipv4')|
                    json_query('[].join(`/`, [network, prefix])')
                    }}"
    
        devs: "{{ ansible_facts|dict2items|
                  selectattr('value.ipv4', 'defined')|
                  selectattr('value.active')|
                  map(attribute='value')|list }}"
    
        _device: "{{ devs|map(attribute='device') }}"
        _ipv4: "{{ devs|map(attribute='ipv4') }}"
        _dict: "{{ _ipv4|items2dict(key_name='network', value_name='prefix') }}"
        _list: "{{ _dict|dict2items }}"
    
        _cidrs: "{{ _dict.keys()|zip(_dict.values())|map('join', '/')|list }}" 
        
      tasks:
    
        - setup:
            gather_subset: network
        - debug:
            var: devs
          when: debug|d(false)|bool
    
        - debug:
            var: result
    
        - debug:
            var: _device
        - debug:
            var: _ipv4
        - debug:
            var: _dict
        - debug:
            var: _list
    
        - debug:
            var: _cidrs