How to use Ansible 'selectattr' Filter?

Ansible offers a wide variety of filters to help users manipulate and manage data, making tasks easier and more efficient. While there are numerous filters available, I find myself frequently using the selectattr filter in the context of Network Automation. Its ability to filter lists of dictionaries based on specific attributes makes it an invaluable tool when working with complex network devices and their configurations.

'selectattr' filter is a feature within Jinja2 that enables filtering a list of objects based on an attribute's presence or value. Let's look at a few examples to better understand this filter.

How to Use Ansible ‘map’ filter
The map filter in Ansible is a powerful tool that lets you modify or transform each item in a list without the need for looping. It’s like giving a single command that all items in the list follow

Example 1 - Using Ansible selectattr Filter

Let's consider a basic example of using the selectattr filter with a list of fruits and their attributes.

---
- name: Simple selectattr Filter Example
  hosts: localhost
  gather_facts: no
  vars:
    fruits:
      - name: apple
        color: red
      - name: banana
        color: yellow
      - name: cherry
        color: red

  tasks:
    - name: Display red fruits
      debug:
        msg: "{{ item.name }} is red."
      loop: "{{ fruits | selectattr('color', 'equalto', 'red') | list }}"
PLAY [Simple selectattr Filter Example] *******************************

TASK [Display red fruits] *****************************************
ok: [127.0.0.1] => (item={'name': 'apple', 'color': 'red'}) => {
    "msg": "apple is red."
}
ok: [127.0.0.1] => (item={'name': 'cherry', 'color': 'red'}) => {
    "msg": "cherry is red."
}

PLAY RECAP ********************************************************
127.0.0.1                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

loop: "{{ fruits | selectattr('color', 'equalto', 'red') | list }}": This line is defining a loop for the Display red fruits task. The loop iterates over a list of red fruits, which is generated using the selectattr filter.

  • fruits: This is the input list of dictionaries, where each dictionary represents a fruit with 'name' and 'color' attributes.
  • selectattr('color', 'equalto', 'red'): This filter is applied to the fruits list, selecting only the dictionaries with a 'color' attribute equal to 'red'.
  • |: The pipe symbol is used to chain filters, taking the output from the previous filter and passing it to the next filter.
  • list: This filter converts the output from the selectattr filter into a list. The conversion is required since the selectattr filter returns an iterable object, which needs to be converted to a list for use in a loop in Ansible.

Example 2 - Create a New List (Entire Dict)

In this example, the selectattr() filter, creates a list called red_fruits that contains the entire dictionary for each red fruit, including both the name and colour attributes.

---
- name: Simple selectattr Filter Example
  hosts: localhost
  gather_facts: no
  vars:
    fruits:
      - name: apple
        color: red
      - name: banana
        color: yellow
      - name: cherry
        color: red

  tasks:
    - name: Create a list of red fruits
      set_fact:
        red_fruits: "{{ fruits | selectattr('color', 'equalto', 'red') | list }}"

    - name: Display red fruits
      debug:
        var: red_fruits
  1. fruits: Our original list
  2. selectattr('color', 'equalto', 'red'): This filter is applied to the fruits list. It selects the dictionaries whose 'color' attribute is equal to 'red'. (picks first and last)
  3. |: The pipe symbol is used to chain filters. It takes the output from the previous filter and passes it to the next filter.
  4. list: This filter converts the output from the previous selectattr filter into a list. It is required because the selectattr filter returns an iterable object, which needs to be converted to a list for further use or display in Ansible.

When combined, the expression creates a new list called red_fruits, containing only those dictionaries from the fruits list with a 'color' attribute equal to 'red'.

PLAY [Simple selectattr Filter Example] **********************************

TASK [Create a list of red fruits] *******************************************
ok: [127.0.0.1]

TASK [Display red fruits] **********************************************
ok: [127.0.0.1] => {
    "red_fruits": [
        {
            "color": "red",
            "name": "apple"
        },
        {
            "color": "red",
            "name": "cherry"
        }
    ]
}

PLAY RECAP **************************************
127.0.0.1                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
How to Use Ansible Loops - With Examples
Loops in Ansible are like shortcuts for doing the same task many times. Instead of repeating the same steps over and over again, we use a loop to do the task as many times

Example 3 - Create a New List (only the names)

Let's say we only want to create a list containing only the names of the red fruits. let's use the map() filter along with selectattr() to create a new list containing the names of red fruits from the given fruits list.

In this playbook, the map() filter is added after the selectattr() filter. The map(attribute='name') extracts the 'name' attribute from each selected fruit dictionary, resulting in a list of fruit names. The list filter then converts the map object to a list.

---
- name: Simple selectattr and map Filter Example
  hosts: localhost
  gather_facts: no
  vars:
    fruits:
      - name: apple
        color: red
      - name: banana
        color: yellow
      - name: cherry
        color: red

  tasks:
    - name: Create a list of red fruit names
      set_fact:
        red_fruit_names: "{{ fruits | selectattr('color', 'equalto', 'red') | map(attribute='name') | list }}"

    - name: Display red fruit names
      debug:
        var: red_fruit_names
  1. selectattr('color', 'equalto', 'red'): This filter is applied to the fruits list. It selects the dictionaries whose 'color' attribute is equal to 'red'.
  2. map(attribute='name'): This filter is applied to the output from the selectattr filter (the red fruits). It extracts the 'name' attribute from each selected dictionary, creating a new sequence with just the fruit names.
  3. list: This filter converts the output from the previous map filter into a list. It is required because the map filter returns an iterable object, which needs to be converted to a list.
PLAY [Simple selectattr and map Filter Example] **********************************

TASK [Create a list of red fruit names] *************************************
ok: [127.0.0.1]

TASK [Display red fruit names] ******************************************
ok: [127.0.0.1] => {
    "red_fruit_names": [
        "apple",
        "cherry"
    ]
}

PLAY RECAP *********************************
127.0.0.1                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

As you can see, the map() filter is a helpful tool used to transform or extract data from lists or dictionaries. It works on a sequence of items, applying an operation or getting a specific attribute from each item, and returns a new sequence of the processed data.

Using it with Network Automation

This filter came in handy when I was trying to use Ansible to configure OSPF on the Cisco routers.

The vars.yml file below, holds the configuration parameters such as description, IP, area and whether or not the interface is passive. To configure an interface as passive in Ansible, you need to pass a list of interfaces to the passive_interfacesparameter. By using selectattr, I was able to easily filter through the list and generated a list.

#vars.yml
---
router_01:
  router_id: "1.1.1.1"
  interfaces:
    - { interface: "GigabitEthernet0/0", desc: "R2", ip: "10.100.12.1", mask: "255.255.255.0", area: 0, passive: false, enabled: true }
    - { interface: "GigabitEthernet0/1", desc: "R4", ip: "10.100.14.1", mask: "255.255.255.0", area: 0, passive: false, enabled: false }
    - { interface: "GigabitEthernet0/2", desc: "R3", ip: "10.100.13.1", mask: "255.255.255.0", area: 0, passive: false, enabled: false }
    - { interface: "GigabitEthernet0/3", desc: "R5", ip: "10.100.15.1", mask: "255.255.255.0", area: 1, passive: false, enabled: false }
    - { interface: "GigabitEthernet0/6", desc: "TEST", ip: "10.100.100.1", mask: "255.255.255.0", area: 1, passive: true, enabled: true }
    - { interface: "Loopback0", desc: "Router ID", ip: "1.1.1.1", mask: "255.255.255.255", area: 0, passive: true, enabled: true }
---
- name: "OSPF"
  hosts: routers
  gather_facts: no
  vars_files: 
    - vars.yml
  
  tasks:
    - name: OSPF process config
      cisco.ios.ios_ospfv2:
        config:
          processes:
            - router_id: "{{ router_id }}"
              process_id: 1
              passive_interfaces: 
                interface:
                  set_interface: True
                  name: "{{ interfaces | selectattr('passive', 'equalto', true) | map(attribute='interface') | list }}"
  1. interfaces: This is the input list of dictionaries, each representing an interface with various attributes, including a 'passive' attribute.
  2. selectattr('passive', 'equalto', true): This filter is applied to the interfaces list. It selects the dictionaries whose 'passive' attribute is equal to true.
  3. |: The pipe symbol is used to chain filters. It takes the output from the previous filter and passes it to the next filter.
  4. map(attribute='interface'): This filter is applied to the output from the selectattr filter (the passive interfaces). It extracts the 'interface' attribute from each selected dictionary, creating a new sequence with just the interface names.
  5. |: The pipe symbol is used again to chain filters.
  6. list: This filter converts the output from the previous map filter into a list. It is required because the map filter returns an iterable object, which needs to be converted to a list for further use or display in Ansible.

As a result, the expression creates a list called passive_interfaces, containing only the names of the interfaces from the interfaces list with a 'passive' attribute equal to 'true'

Running this playbook will result in the following.

passive_interfaces: 
  interface:
    set_interface: True
    name:
      - "GigabitEthernet0/6"
      - "Loopback0"

The following is the generated config on the Cisco router.

router ospf 1
 router-id 1.1.1.1
 passive-interface GigabitEthernet0/6
 passive-interface Loopback0

I hope you find this blog post useful, if you have any questions, please let me know in the comments.