如何在 Ansible Filter Plugin 中正确复用本地工具函数

ansible 的 filter plugin 不支持直接导入同目录下的普通 python 模块,需通过自定义 collection 结构,将共享逻辑放入 module_utils 目录,再以标准命名空间方式导入,从而实现跨 filter 的代码复用。

在 Ansible 中,filter plugin 本质上是被动态加载的 Python 模块,其加载机制严格限定于 filter_plugins/ 目录下的合法插件文件(即必须含 FilterModule 类且实现 filters() 方法)。因此,将工具函数放在同一目录下的 tools.py 中并尝试 import tools 是无效的——Ansible 会将其误判为待加载的插件(触发 FilterModule 属性缺失警告),而 Python 解释器也无法通过常规路径解析该模块。

官方明确不支持 filter_plugins/ 内部模块互导,也未提供类似 module_utils 对 filter 的原生支持(该机制仅适用于 modules/)。但幸运的是,Ansible Collections 提供了标准化、可复用、且被完整支持的替代方案:将 filter plugin 与共享工具代码共同组织为一个本地 collection,并利用 collection 内部的 plugins/module_utils/ 目录存放可导入的工具模块

✅ 正确实践结构如下(以 larsks.myplugins 为例):

./
├── playbook.yaml
└── collections/
    └── ansible_collections/
        └── larsks/
            └── myplugins/
                └── plugins/
                    ├── filter/
                    │   └── myfilter.py      # 实际 filter 插件
                    └── module_utils/
                        └── tools.py         # 共享工具函数(含 __init__.py 可选)

其中 collections/ansible_collections/larsks/myplugins/plugins/module_utils/tools.py 内容示例:

# tools.py
def shared_function(data_to_process):
    """通用数据处理逻辑,可在多个 filter 中复用"""
    return data_to_process.strip().replace(" ", "_").lower()

对应 filter 插件 myfilter.py:

# myfilter.py
from ansible_collections.larsks.myplugins.plugins.module_utils.tools import shared_function

class FilterModule:
    def filters(self):
        return {
            'normalize': lambda s: shared_function(s),
            'prefix_upper': lambda s: f"PREFIX_{shared_function(s).upper()}"
        }

在 playbook 中使用时,需带上 collection 命名空间:

- hosts: localhost
  gather_facts: false
  tasks:
    - debug:
        msg: "{{ 'Hello World' | larsks.myplugins.normalize }}"
    - debug:
        msg: "{{ 'Test Data' | larsks.myplugins.prefix_upper }}"

⚠️ 注意事项:

  • collections/ 目录必须与 playbook 文件同级,且路径必须严格为 collections/ansible_collections/{namespace}/{collection_name}/...;
  • module_utils/ 下的模块无需 __init__.py(Ansible 2.10+ 自动识别),但添加空 __init__.py 也不影响;
  • 所有导入语句必须使用完整 collection 命名空间路径(ansible_collections.{ns}.{coll}.plugins.module_utils.xxx),不可简写;
  • 本地 collection 无需发布或安装,Ansible 运行时自动发现并加载;
  • 若需多 filter 复用,可将 tools.py 拆分为多个模块(如 string_utils.py, json_utils.py),保持高内聚低耦合。

该方案符合 Ansible 最佳实践,具备可移植性、可测试性与团队协作友好性,是解决 filter 插件代码复用问题的唯一推荐路径。