Custom dict operation filters

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]