过滤器插件是什么
过滤器是 Jinja2 表达式里用竖线调用的函数,模板渲染与 playbook 中的变量表达式都会用到它们。当内置过滤器不够用时(例如要按公司规范格式化资产编号、解析自定义时间串、做特定的地址计算),就写一个过滤器插件。它运行在控制节点,纯函数式地取一个值返回一个值,不接触目标机。
基本信息
| 放置位置 | 项目或角色下的 filter_plugins/ 目录,集合则放 plugins/filter/ |
|---|---|
| 注册方式 | 插件文件里定义类并提供 filters 方法,返回“名字 → 可调用对象”的映射 |
| 同类扩展 | test 插件扩展的是“is”判断,写法与过滤器类似 |
| 调用位置 | 模板文件与 playbook 变量表达式,写法都是值加竖线加过滤器名 |
| 错误处理 | 抛专用异常类型,让报错信息能定位到过滤器而不是模板行 |
| 共享代码 | 与模块共用的工具函数放 module_utils,避免两套实现 |
典型使用场景
- 统一命名规范:把主机名、资产编号转换成公司约定的展示格式
- 结构化数据的再加工:从嵌套字典里取出需要的键并拼成配置行
- 自定义校验:把“是否合法网段”这类判断做成过滤器在模板里复用
- 兼容历史格式:旧系统配置文件有特殊字段格式,用过滤器在渲染时转换
- 减少模板判断:把多分支的取值逻辑收敛到过滤器内部,模板保持简洁
- 跨项目复用:把通用过滤器按集合打包,多个仓库共同引用
写法与调用
写法与调用
# 结构
# filter_plugins/app_filters.py 项目内临时使用
# plugins/filter/app_filters.py 按集合打包(推荐)
#
# 文件内三件事:
# 1) 定义普通函数实现逻辑,保持纯函数:同输入必得同输出
# 2) 参数校验放在函数入口,非法输入抛专用异常类型
# 3) 定义注册类与 filters 方法,把名字映射到函数
#
# 模板中调用(名字与注册名一致)
# {{ inventory_hostname | app_display_name }}
# {{ app_list | app_join_configs(', ') }}
#
# playbook 表达式里同样可用
# vars:
# label: "{{ ansible_host | app_display_name }}"
#
# 校验:模板渲染任务在检查模式下跑一次,看渲染结果是否符合预期。与其它扩展方式的取舍
| 做法 | 优点 | 代价 |
|---|---|---|
| 写成过滤器插件 | 模板与表达式里都能复用,写法统一 | 逻辑只处理数据,无法访问目标机 |
| 写成模块 | 能改目标机状态 | 需要一次远程执行,不适合纯数据加工 |
| 模板内联判断 | 不用新增文件 | 模板迅速变复杂,多处重复同一逻辑 |
| 写成 lookup 插件 | 可以读取外部数据源 | 有副作用或需要网络时判断更复杂 |
| 打包进集合 | 可版本化、可分发、带文档 | 需要维护集合结构与发布流程 |
过滤器名要与内置名字区分开,自定义集合的过滤器通常带命名空间前缀调用。名字撞车时会静默改变原有行为,排查成本很高,用前缀是最省事的规避方式。
过滤器运行在控制节点,能读到控制节点上的环境与文件。不要在其中做时间依赖、随机数、外部命令调用这类有副作用的行为:它会被模板多次渲染调用,结果不可预测,也会让检查模式失去意义。
开发与验证习惯
先写函数、用几条边界输入验证输出,再注册成过滤器,最后放入模板使用。边界至少覆盖:空值、非预期类型、超长输入。把这几条写成小测试留在仓库里,后续新人改动逻辑时能立刻发现回归,比依赖模板渲染结果目测更可靠。