pythonansible

Using local library in Ansible filter_plugin


I am creating a bunch of filter plugins for Ansible (2.9/2.13) and I would like to have a tools library that can be used by these filter plugins.

I have the directory filter_plugins under my playbook directory and in there, I have a bunch of python scripts that provide Ansible with various filters. This works fine.

Now, some of these python scripts have the same functions. Naturally, I would like to move these shared function to a separate library file. So here is what I tried:

[WARNING]: Skipping plugin (/<REDACTED>/playbook_dir/filter_plugins/tools.py) as it seems to be invalid: module 'ansible.plugins.filter.966997458576792353_tools' has no attribute 'FilterModule'
[WARNING]: Skipping plugin (/<REDACTED>/playbook_dir/filter_plugins/check.py) as it seems to be invalid: No module named 'tools'

All the filter scripts look basically the same (some have more filters, some have less):

#!/usr/bin/python

class FilterModule(object):
    def filters(self):
        return {
            'filter1': self.filter1,
            'filter2': self.filter2
        }

    def shared_function(data_to_process):
        processed_data = <process_data>
        return processed_data

    def filter1(self, in_data):
        """some code to process in_data to out_data"""
        process_data = <do_some_stuff>
        out_data = shared_function(process_data)
        return out_data

    def filter2(self, in_data):
        """some other code to process in_data to out_data"""
        process_data = <do_some_different_stuff>
        out_data = shared_function(process_data)
        return out_data

As shared_function is used in more than one filter script, I want to move it out of there to the tools.py library

Can anybody help me find the correct way to include local library files in filter plugins for Ansible?

Thanx for your help.


Solution

  • If you were writing an Ansible module, the answer would be to place your shared code inside the module_utils directory. Unfortunately, this solution isn't available to plugins such as filters.

    Issue #28770 has some conversation around this topic, and the recommended solution is to use a playbook-adjacent collection. A collection is a set of resources including plugins, modules, roles, playbooks, etc.

    As described in the documentation, you can include a collection "adjacent to" (in the same directory as) your playbook:

    You can also keep a collection adjacent to the current playbook, under a collections/ansible_collections/ directory structure.

    playbook.yaml
    ├── collections/
    │   └── ansible_collections/
    │               └── my_namespace/
    │                   └── my_collection/<collection structure lives here>
    

    I'm going to add a collection larsks.myplugins to my local directory, and then create a filter plugin and some shared code. I'll use the following structure:

    .
    ├── collections
    │   └── ansible_collections
    │       └── larsks
    │           └── myplugins
    │               └── plugins
    │                   ├── filter
    │                   │   └── myplugin.py
    │                   └── module_utils
    │                       └── mycommon.py
    └── playbook.yaml
    

    In mycommon.py I have:

    def mysharedfunction(v):
        return v.upper()
    

    In myplugin.py I have:

    from ansible_collections.larsks.myplugins.plugins.module_utils.mycommon import mysharedfunction
    
    class FilterModule:
        def filters(self):
            return {'myfilter': mysharedfunction}
    

    This allows me to write a playbook like this:

    - hosts: all
      gather_facts: false
      tasks:
        - debug:
            msg: "{{ 'this is a test' | larsks.myplugins.myfilter }}"
    

    So there you have a filter plugin importing shared code from another module.