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.
저자 소개
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.
유사한 검색 결과
Slash VM provisioning time on Red Hat Openshift Virtualization using Red Hat Ansible Automation Platform
Red Hat Ansible Automation Platform: Measuring Business Impact with Dashboard and Analytics
Technically Speaking | Taming AI agents with observability
You Can’t Automate Expectations | Code Comments
채널별 검색
오토메이션
기술, 팀, 인프라를 위한 IT 자동화 최신 동향
인공지능
고객이 어디서나 AI 워크로드를 실행할 수 있도록 지원하는 플랫폼 업데이트
오픈 하이브리드 클라우드
하이브리드 클라우드로 더욱 유연한 미래를 구축하는 방법을 알아보세요
보안
환경과 기술 전반에 걸쳐 리스크를 감소하는 방법에 대한 최신 정보
엣지 컴퓨팅
엣지에서의 운영을 단순화하는 플랫폼 업데이트
인프라
세계적으로 인정받은 기업용 Linux 플랫폼에 대한 최신 정보
애플리케이션
복잡한 애플리케이션에 대한 솔루션 더 보기
가상화
온프레미스와 클라우드 환경에서 워크로드를 유연하게 운영하기 위한 엔터프라이즈 가상화의 미래