In this post I will show you how to develop a Ansible custom filter plugin that transforms live data obtained from a network device into the correct format for further use in subsequent Ansible playbook tasks.

What are Ansible filters?

Ansible filters are powerful Python functions for manipulating data within Ansible. Filters receive input data via a variable, then perform a fixed operation and returns the result. Use cases are combining lists, transforming JSON data, manipulating strings, parsing data, …

Within Ansible there are three types of filters:

  • Ansible-specific filters (present in core.py)
  • Official built-in Jinja2 filters
  • Custom filter plugins

An example of the built-in “dict2items” filter in ansible playbook task:

- name:  VLAN >> parsed with custom filter
  set_fact:
    vlans_live: "{{ vlans | dict2items }}"

Develop a custom Ansible filter for JSON manipulation

In this example, we receive an overview of the configured switch vlans via REST API. The JSON response is a nested dictionary structure that we want to transform through our custom filter plugin “nesteddict2items” into a simple list of dictionaries containing the desired key: value pairs. This to structure and prepare the data for further Ansible Playbook tasks that go through the vlan list via the loop method, to perform for example configuration validation or generation.

vlan output requested from the device:

"vlans": {
    "1": {
        "vlanDescription": "vlan-1",
        "vlanNumber": "1"
    },
    "10": {
        "vlanDescription": "vlan-10",
        "vlanNumber": "10"
    }
}

vlan output after parsing with the “nesteddict2items” filter:

"vlans": [
    {
        "vlanDescription": "vlan-1",
        "vlanNumber": "1"
    },
    {
        "vlanDescription": "vlan-10",
        "vlanNumber": "10"
    }
]

1. Create a Python function

Using the built-in Ansible filter “dict2items” it is possible to transform a dictionary into a list, but this filter does not give us the desired result in combination with a nested dictionary. We need a simple Python function that takes the value of each dictionary item and adds it to a new list.

Create the file ’nesteddict2items.py’ with the following code and place it in the folder “filter_plugins” within your project.

#!/usr/bin/python

class FilterModule(object):
    ''' Nested dict filter '''

    def filters(self):
        return {
            'nesteddict2items': self.nesteddict2items
        }

    def nesteddict2items(self, vlans_live):
        vlans = []

        for v_key, v_value in vlans_live.items():
            vlans.append(v_value)

        return vlans

2. Add filter_plugins path folder to ansible.cfg

To make effective use of the plugin, the only thing left to do is to refer to our new plugin folder via ansible.cfg. Remove the comment from the “filter_plugins” line and add the path “filter_plugins” so that Ansible’s filter search path can find our custom plugin file.

# set plugin path directories here, separate with colons
#action_plugins     = /usr/share/ansible/plugins/action
#become_plugins     = /usr/share/ansible/plugins/become
#cache_plugins      = /usr/share/ansible/plugins/cache
#callback_plugins   = /usr/share/ansible/plugins/callback
#connection_plugins = /usr/share/ansible/plugins/connection
#lookup_plugins     = /usr/share/ansible/plugins/lookup
#inventory_plugins  = /usr/share/ansible/plugins/inventory
#vars_plugins       = /usr/share/ansible/plugins/vars
filter_plugins      = filter_plugins
#test_plugins       = /usr/share/ansible/plugins/test
#terminal_plugins   = /usr/share/ansible/plugins/terminal
#strategy_plugins   = /usr/share/ansible/plugins/strategy

3. Custom filter in Ansible Playbooks

Now you can use the filter in your Ansible Playbook. Filters can be used consecutively, so the possibilities are endless.

Python type testing via type_debug filter

he built-in filter “type_debug” stores the underlying Python data type for our variable. We add these to check and illustrate whether the Python dictionary has been effectively converted to a list.

Python type without filter transformation:

Ansible Playbook task:
- name:  DEBUG >> vlan python type
  debug:
    var: vlans_type: "{{ vlans | type_debug }}"

Terminal output:
TASK [DEBUG >> vlan parsed]
 ok: [NET-SWI-001] => {
     "vlans_type": "dict"
 }

Python type with filter transformation:

Ansible Playbook task:
- name:  DEBUG >> vlan python type
  debug:
    var: vlans_type: "{{ vlans | nesteddict2items | type_debug }}"

Terminal output:
TASK [DEBUG >> vlan parsed]
 ok: [NET-SWI-001] => {
     "vlans_type": "list"
 }