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.
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 thefruits
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 theselectattr
filter into a list. The conversion is required since theselectattr
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
fruits
: Our original listselectattr('color', 'equalto', 'red')
: This filter is applied to thefruits
list. It selects the dictionaries whose 'color' attribute is equal to 'red'. (picks first and last)|
: The pipe symbol is used to chain filters. It takes the output from the previous filter and passes it to the next filter.list
: This filter converts the output from the previousselectattr
filter into a list. It is required because theselectattr
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
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
selectattr('color', 'equalto', 'red')
: This filter is applied to thefruits
list. It selects the dictionaries whose 'color' attribute is equal to 'red'.map(attribute='name')
: This filter is applied to the output from theselectattr
filter (the red fruits). It extracts the 'name' attribute from each selected dictionary, creating a new sequence with just the fruit names.list
: This filter converts the output from the previousmap
filter into a list. It is required because themap
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_interfaces
parameter. 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 }}"
interfaces
: This is the input list of dictionaries, each representing an interface with various attributes, including a 'passive' attribute.selectattr('passive', 'equalto', true)
: This filter is applied to theinterfaces
list. It selects the dictionaries whose 'passive' attribute is equal totrue
.|
: The pipe symbol is used to chain filters. It takes the output from the previous filter and passes it to the next filter.map(attribute='interface')
: This filter is applied to the output from theselectattr
filter (the passive interfaces). It extracts the 'interface' attribute from each selected dictionary, creating a new sequence with just the interface names.|
: The pipe symbol is used again to chain filters.list
: This filter converts the output from the previousmap
filter into a list. It is required because themap
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.