Custom dict operation filters #
Filters are not limited to text.
Ansible makes it easy to write dict vars like this:
example_var:
dict_key_1: The value of dict key one
another_key: This is another value
Et: cetera
And filters can work on these variables as well. You might think of them as “dictionaries”, “associative arrays”, “hashtables”, “hashmaps”, or the like.
However, it is not always easy to modify these dicts, depending on what you want to do. You can do it with set_fact
, builtin filters, and a lot of swearing, but the result is an unreadable mess. Instead, consider writing a custom filter that does exactly the operation you want, and provide good documentation and examples.
TODO
There are built in filters that can do some of this, but not all of it; discuss them here
Instead, I recommend writing code and (if necessary) copious documentation so that you remember why specifically you needed this code.
TODO
this kind of code is different than application code, and very specific documentation is helpful
TODO
Type check the dictlist helper filters
#!/usr/bin/python3
class FilterModule(object):
def filters(self):
return {
"dictlist_combine_uniqkey": self.dictlist_combine_uniqkey,
"dictlist_with_defaults": self.dictlist_with_defaults,
}
def dictlist_combine_uniqkey(self, lista, listb, key):
"""Combine two lists of dicts using a unique key.
Create a new list from two input lists where each element in the list is a dict.
Use one element in each dict as the identifier.
If a dict with the same identifier exists in both lists,
the dict in the second list replaces the dict in the first.
Otherwise, the dicts are appended.
Usage:
list1 | dictlist_combine_uniqkey(list2, key)
Example:
list1:
- src: /tmp/felix
dest: /etc/felix
- src: /var/tmp/francis
dest: /etc/francis
list2:
- src: /home/clou/felix
dest: /etc/felix
- src: /home/clou/billiam
dest: /etc/billiam
---
- name: combine two lists using a unique key
set_fact:
copylist: {{ dictlist_combine_uniqkey: list1 | dictlist_combine_uniqkey(list2, 'dest') }}
---
Example result:
copylist:
- src: /home/clou/felix
dest: /etc/felix
- src: /var/tmp/francis
dest: /etc/francis
- src: /home/clou/billiam
dest: /etc/billiam
"""
result = {}
lista = lista if isinstance(lista, list) else [lista]
listb = listb if isinstance(listb, list) else [listb]
listb_uniqkeys = [bitem[key] for bitem in listb]
result = [aitem for aitem in lista if aitem[key] not in listb_uniqkeys]
result += listb
return result
def dictlist_with_defaults(self, dictlist, defaults):
"""For each dict in a list, provide a set of defaults, and return a new dict list.
Given a list of dicts and a default dict,
return a new list with the defaults filled in for any key not provided.
Usage:
dictlist | dictlist_with_defaults(defaults)
Example:
initial_dictlist:
- name: slappy
mode: overdrive
lock: false
- name: mappy
lights: on
default_dict:
name: ""
mode: normal
lock: true
lights: off
---
- name: Apply defaults to a dictlist
set_fact:
final_dictlist: "{{ initial_dictlist | dictlist_with_defaults(default_dict) }}
---
Example result:
final_dictlist:
- name: slappy
mode: overdrive
lock: false
lights: off
- name: mappy
lights: on
mode: overdrive
lock: false
"""
return [{**defaults, **dictlist_item} for dictlist_item in dictlist]