My friend and peer Roberto Nozaki recently wrote How to work with lists and dictionaries in Ansible. After reading it, this felt like a perfect opportunity to follow up with some related content. If you're unsure of what dictionaries or lists are in Ansible, give his article a quick read and then come back here. Otherwise, keep reading, as I'm confident you'll find it worthwhile if you're new to using Jinja filtering.
[ Get started with IT automation with the Ansible Automation Platform beginner's guide. ]
The Jinja go-to filters
When you're working with Ansible, you typically work with lists and dictionaries. On occasion, the data you're working with is actually a list of dictionaries. This is where Jinja filtering comes in handy.
Jinja is powerful templating language that has two filters that are ideal for extracting data from lists of dictionaries. They are: selectattr()
and map()
.
The Jinja documentation explains the filters like so:
selectattr()
filters a sequence of objects by applying a test to the specified attribute of each object and only selecting the objects when the test succeeds.map()
applies a filter on a sequence of objects or looks up an attribute. This is useful when you have lists of objects but you are interested in only a certain value.
Learning how to use these filters properly will take your Ansible skills to the next level. To be fair, there are a lot of Jinja filters. In this article, I focus on selectattr()
and map()
.
While some Ansible users prefer to use the json_query
filter, I personally prefer to use Jinja's selectattr()
and map()
filters. Ansible natively supports Jinja. Since selectattr()
and map()
are Jinja filters, you don't need to add anything to your server if Ansible is installed.
The json_query
module, however, is part of the community.general
collection. Thus, it is not natively installed with Ansible. You may not have the luxury of adding collections on the fly without some level of approval beforehand. Using Jinja filters mitigates the immediate need for external collections to extract data from lists of dictionaries.
[ Download now: A system administrator's guide to IT automation. ]
Get started with filters
First, create a variable called bands
. The variable bands
is a list of dictionaries that will be used in the example.yml
playbook. As you put the playbook together, you'll begin to see how to select data from the variable.
# List of dictionaries
bands:
- name: The Beatles
members:
- Lennon
- McCartney
- Harrison
- Starr
formed: 1960
decade: 60s
- name: The Eagles
members:
- Frey
- Henley
- Leadon
- Meisner
formed: 1971
decade: 70s
- name: Run DMC
members:
- Simmons
- McDaniels
- Mizell
formed: 1983
decade: 80s
- name: Red Hot Chili Peppers
members:
- Kiedis
- Smith
- Frusciante
- Balzary
formed: 1982
decade: 90s
- name: "Destiny's Child"
members:
- Knowles
- Rowland
- Williams
formed: 1990
decade: 00s
- name: "Black Eyed Peas"
members:
- Adams
- Lindo
- Gomez
formed: 1995
decade: 00s
...
Next, create a set of tasks that show what kind of data you are dealing with. In the first task, you should see that the bands
variable itself is a list. The second task should show the bands
variable is made up of dictionaries. Simply put, this shows you're dealing with a list of dictionaries.
- name: Show what type of variable bands is
ansible.builtin.debug:
msg: "{{ bands | type_debug }}"
- name: Show what type of data the bands variable is made up of
ansible.builtin.debug:
msg: "{{ item | type_debug }}"
loop: "{{ bands }}"
For the next task, list all members of all bands. You're selecting the members
key of the dictionary if the members
key is "defined." In this case you know it is, so you can expect data to be returned. Once you select the data, pass it to the map
filter. The map
filter applies a filter on a sequence of objects or looks up an attribute. In this case, you're looking up the members
attribute because that's the data you're interested in:
- name: List ALL band members of ALL bands
ansible.builtin.debug:
msg: "{{ bands | selectattr('members', 'defined') | map(attribute='members') }}"
Say you're interested in when the bands were formed rather than who the members are. To do so, create a task to find the years that the bands were formed, regardless of other key/value pairs:
- name: List the years the bands were formed
ansible.builtin.debug:
msg: "{{ bands | selectattr('formed', 'defined') | map(attribute='formed') }}"
Or maybe you want to know the members of a band that formed in a specific year. In this task, you're looking for the members of the band that was formed in the year 1971:
- name: List members of band formed in 1971
ansible.builtin.debug:
msg: "{{ bands | selectattr('formed', 'match', '1971') | map(attribute='members') }}"
For the next task, find the name of a band that has a member named "Starr." When doing searches like this, the search/match parameters are case sensitive. For this example, you're specifically looking for the name of the band in which "Starr" was a member:
- name: List band formed with a member named Starr
ansible.builtin.debug:
msg: "{{ bands | selectattr('members', 'search', 'Starr') | map(attribute='name') }}"
What if more than one list of dictionaries has the same data? No problem! You can grab as much or as little as you wish. The first example below grabs all decades that start with a single character followed by "0" and ending with "s." The second example grabs all band names that have "00" in the "decade" value.
- name: List band name when band decade ends with 0s regardless of the decade
ansible.builtin.debug:
msg: "{{ bands | selectattr('decade', 'search', '^.0s$') | map(attribute='name') }}"
- name: List band name when band decade contains 00 in the name
ansible.builtin.debug:
msg: "{{ bands | selectattr('decade', 'search', '00') | map(attribute='name') }}"
[ Write your first Ansible playbook in this hands-on interactive lab. ]
Put everything together in a playbook
Now that the tasks are defined, put it altogether in a single playbook:
---
- name: 'Jinja selectattr() and map() example with list of dictionaries'
hosts: localhost
gather_facts: false
vars:
# This is a list of dictionaries
bands:
- name: The Beatles
members:
- Lennon
- McCartney
- Harrison
- Starr
formed: 1960
decade: 60s
- name: The Eagles
members:
- Frey
- Henley
- Leadon
- Meisner
formed: 1971
decade: 70s
- name: Run DMC
members:
- Simmons
- McDaniels
- Mizell
formed: 1983
decade: 80s
- name: Red Hot Chili Peppers
members:
- Kiedis
- Smith
- Frusciante
- Balzary
formed: 1982
decade: 90s
- name: "Destiny's Child"
members:
- Knowles
- Rowland
- Williams
formed: 1990
decade: 00s
- name: "Black Eyed Peas"
members:
- Adams
- Lindo
- Gomez
formed: 1995
decade: 00s
tasks:
- name: Show what type of variable bands be
ansible.builtin.debug:
msg: "{{ bands | type_debug }}"
- name: Show what type of data the bands variable consists of
ansible.builtin.debug:
msg: "{{ item | type_debug }}"
loop: "{{ bands }}"
- name: List band members of all bands
ansible.builtin.debug:
msg: "{{ bands | selectattr('members', 'defined') | map(attribute='members') }}"
- name: List the years the bands were formed
ansible.builtin.debug:
msg: "{{ bands | selectattr('formed', 'defined') | map(attribute='formed') }}"
- name: List members of band formed in 1971
ansible.builtin.debug:
msg: "{{ bands | selectattr('formed', 'match', '1971') | map(attribute='members') }}"
- name: List band with a member named Starr
ansible.builtin.debug:
msg: "{{ bands | selectattr('members', 'search', 'Starr') | map(attribute='name') }}"
- name: List band name when band decade ends with 0s regardless of the decade
ansible.builtin.debug:
msg: "{{ bands | selectattr('decade', 'search', '^.0s$') | map(attribute='name') }}"
- name: List band name when band decade contains 00 in the name
ansible.builtin.debug:
msg: "{{ bands | selectattr('decade', 'search', '00') | map(attribute='name') }}"
- name: Loop through the the 80s and 90s bands and display a message if Run DMC is in the name
ansible.builtin.debug:
msg:
- !unsafe It's Tricky to rock a rhyme
loop: "{{ bands | selectattr('decade', 'search', '^[8-9]0s$') | map(attribute='name') }}"
when: "'run' in item | lower"
...
Execute the playbook and check the results
Execute the playbook to see the filters in action.
Note that I performed this test on a Fedora 37 host with Ansible 7.3. Be sure your version of Jinja supports the arguments you're using for selectattr()
if you want to replicate this exercise in your own environment.
$ ansible-playbook example.yml
PLAY [Jinja selectattr() and map() example with list of dictionaries] *********
TASK [Show what type of variable bands be] ************************************
ok: [localhost] => {
"msg": "list"
}
TASK [Show what type of data the bands variable consists of] ******************
ok: [localhost] => (item={'name': 'The Beatles', 'members': ['Lennon', 'McCartney', 'Harrison', 'Starr'], 'formed': 1960, 'decade': '60s'}) => {
"msg": "dict"
}
ok: [localhost] => (item={'name': 'The Eagles', 'members': ['Frey', 'Henley', 'Leadon', 'Meisner'], 'formed': 1971, 'decade': '70s'}) => {
"msg": "dict"
}
ok: [localhost] => (item={'name': 'Run DMC', 'members': ['Simmons', 'McDaniels', 'Mizell'], 'formed': 1983, 'decade': '80s'}) => {
"msg": "dict"
}
ok: [localhost] => (item={'name': 'Red Hot Chili Peppers', 'members': ['Kiedis', 'Smith', 'Frusciante', 'Balzary'], 'formed': 1982, 'decade': '90s'}) => {
"msg": "dict"
}
ok: [localhost] => (item={'name': "Destiny's Child", 'members': ['Knowles', 'Rowland', 'Williams'], 'formed': 1990, 'decade': '00s'}) => {
"msg": "dict"
}
ok: [localhost] => (item={'name': 'Black Eyed Peas', 'members': ['Adams', 'Lindo', 'Gomez'], 'formed': 1995, 'decade': '00s'}) => {
"msg": "dict"
}
TASK [List band members of all bands] *****************************************
ok: [localhost] => {
"msg": [
[
"Lennon",
"McCartney",
"Harrison",
"Starr"
],
[
"Frey",
"Henley",
"Leadon",
"Meisner"
],
[
"Simmons",
"McDaniels",
"Mizell"
],
[
"Kiedis",
"Smith",
"Frusciante",
"Balzary"
],
[
"Knowles",
"Rowland",
"Williams"
],
[
"Adams",
"Lindo",
"Gomez"
]
]
}
TASK [List the years the bands were formed] ***********************************
ok: [localhost] => {
"msg": [
1960,
1971,
1983,
1982,
1990,
1995
]
}
TASK [List members of band formed in 1971] ************************************
ok: [localhost] => {
"msg": [
[
"Frey",
"Henley",
"Leadon",
"Meisner"
]
]
}
TASK [T08 - List band with a member named Starr] ******************************
ok: [localhost] => {
"msg": [
"The Beatles"
]
}
TASK [List band name when band decade ends with 0s regardless of the decade] **
ok: [localhost] => {
"msg": [
"The Beatles",
"The Eagles",
"Run DMC",
"Red Hot Chili Peppers",
"Destiny's Child",
"Black Eyed Peas"
]
}
TASK [List band name when band decade contains 00 in the name] ****************
ok: [localhost] => {
"msg": [
"Destiny's Child",
"Black Eyed Peas"
]
}
TTASK [Loop through the the 80s and 90s bands and display a message if Run DMC is in the name] *****
ok: [localhost] => (item=Run DMC) => {
"msg": [
"It's Tricky.....to rock a rhyme"
]
}
skipping: [localhost] => (item=Red Hot Chili Peppers)
PLAY RECAP ********************************************************************
localhost : ok=9 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Wrap up
Manipulating lists of dictionaries is a critical skill when automating tasks with Ansible or Red Hat Ansible Automation Platform (AAP), but it can be intimidating when you're first learning how to use it.
Copy this example and practice using Jinja filtering as part of your skills development. Hopefully this article gave you enough of a glimpse to increase your interest in using Jinja filtering.
About the author
Randy Romero is a Senior Ansible Consultant and Red Hat Certified Architect at Red Hat where he specializes in IT automation with Ansible and Ansible Automation Platform. He has experience in the cable, insurance, and loyalty-marketing industries, having performed multiple roles starting with junior Systems Administrator all the way to Cloud Architect with over 27 years of experience in the IT industry.
Randy fervently advocates for automation with Ansible to increase an individual's ability to grow by removing mundane repeatable tasks that tend to stand in the way of a person's own professional development and growth. He is also motivated in his mission to evangelize automation through Ansible by changing the minds of people who say "This is how we've always done it'. In his spare time when he is not studying or working, he enjoys mentoring individuals (young and old) seeking to achieve their Red Hat Certified System Administrator and Red Hat Certified Engineer certifications. When he's not doing IT related things he enjoys watching/reading all things paranormal.
Browse by channel
Automation
The latest on IT automation for tech, teams, and environments
Artificial intelligence
Updates on the platforms that free customers to run AI workloads anywhere
Open hybrid cloud
Explore how we build a more flexible future with hybrid cloud
Security
The latest on how we reduce risks across environments and technologies
Edge computing
Updates on the platforms that simplify operations at the edge
Infrastructure
The latest on the world’s leading enterprise Linux platform
Applications
Inside our solutions to the toughest application challenges
Original shows
Entertaining stories from the makers and leaders in enterprise tech
Products
- Red Hat Enterprise Linux
- Red Hat OpenShift
- Red Hat Ansible Automation Platform
- Cloud services
- See all products
Tools
- Training and certification
- My account
- Customer support
- Developer resources
- Find a partner
- Red Hat Ecosystem Catalog
- Red Hat value calculator
- Documentation
Try, buy, & sell
Communicate
About Red Hat
We’re the world’s leading provider of enterprise open source solutions—including Linux, cloud, container, and Kubernetes. We deliver hardened solutions that make it easier for enterprises to work across platforms and environments, from the core datacenter to the network edge.
Select a language
Red Hat legal and privacy links
- About Red Hat
- Jobs
- Events
- Locations
- Contact Red Hat
- Red Hat Blog
- Diversity, equity, and inclusion
- Cool Stuff Store
- Red Hat Summit