我正在编写一个 Ansible 过滤器。有没有可能这个过滤器返回
Undefined
,所以结果输出是Undefined
?
我的目标是在以下情况下可以将过滤器与
default
或 select
结合使用:
my_variable: "{{ my_input | myfilter | default(something) }}"
或:
my_list: "{{ list_of_things | map('myfilter') | select | list }}"
例如:
- vars:
area: 9
side: "{{ area | my_sqrt | default(0) }}"
debug:
var: side
或:
- vars:
numbers: [16, 9, -4]
roots: "{{ numbers | map('my_sqrt') | select | list }}"
debug:
var: roots
其中
map('my_sqrt')
应输出 [4, 3, Undefined]
且 select
应将其转换为 [4, 3]
。
我尝试将
my_sqrt
定义为:
import math
from jinja2.runtime import Undefined
def my_sqrt(input):
if input < 0:
return Undefined
else:
return math.sqrt(input)
class FilterModule(object):
def filters(self):
return { 'my_sqrt': my_sqrt }
但是,当我执行上面的剧本时,它似乎返回字符串
<class 'jinja2.runtime.Undefined'>
。
进一步的测试证明了这一点:
('never' if 0) | default('the_default')
确实返回了the_default
。
然而,
-4 | my_sqrt | default('the_default')
遗憾地返回了字符串"<class 'jinja2.runtime.Undefined'>"
。
我都尝试过
jinja2.runtime.Undefined
和 ansible.template.AnsibleUndefined
。
我主要想知道 Ansible/Jinja2 过滤器是否可以返回
Undefined
。
如果这是不可能的,我有兴趣听到替代方法。我实际上正在编写的是一个过滤器,它接受一个对象和一个选项列表(可能的匹配),并将返回最佳选项(最佳匹配)。如果没有好的选择,它应该返回
Undefined
,之后由剧本来处理这种情况。作为基本要求,它应该适用于 default
和 select
。
您可以将
filter
定义为返回 None
而不是 Undefined
。然后,您可以将输出通过管道传输到 select
和 default
过滤器,但需要注意。
None
将被 default
过滤器解释为布尔值。因此,您必须将值 true
作为 default
过滤器的第二个参数传递,才能使其按预期工作。
使用此代码尝试一下:
过滤器定义:
#!/bin/bash
import math
import typing as t
V = t.TypeVar("V")
def my_sqrt(value: V) -> t.Union[V , None]:
if value < 0:
return None
else:
return math.sqrt(value)
class FilterModule(object):
def filters(self):
return {
'my_sqrt': my_sqrt
}
剧本示例:
- hosts: localhost
gather_facts: no
tasks:
- name: Test the my_sqrt filter
debug:
msg: "{{ item | my_sqrt | default(0, true) }}"
loop:
- 4
- -1
- name: Select
debug:
msg: "{{ [4, 3, -4] | map('my_sqrt') | select | list }}"
您会看到两个
tasks
都按预期工作:
PLAY [localhost] ***************************************************************
Friday 14 May 2021 09:27:41 -0300 (0:00:00.019) 0:00:00.019 ************
TASK [Test the my_sqrt filter] *************************************************
ok: [localhost] => (item=4) =>
msg: '2.0'
ok: [localhost] => (item=-1) =>
msg: '0'
Friday 14 May 2021 09:27:41 -0300 (0:00:00.042) 0:00:00.062 ************
TASK [Select] ******************************************************************
ok: [localhost] =>
msg:
- 2.0
- 1.7320508075688772
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Friday 14 May 2021 09:27:41 -0300 (0:00:00.044) 0:00:00.106 ************
===============================================================================
Select ------------------------------------------------------------------ 0.04s
Test the my_sqrt filter ------------------------------------------------- 0.04s
这在默认设置下(非 Jinja 原生)似乎是不可能的,因为 Ansible 非常努力将返回值粉碎为字符串类型。 (至少我做不到,除了以感觉脆弱的方式对 Ansible 核心进行猴子修补。)
另一方面,如果您在包装器 shell 脚本中设置
ANSIBLE_JINJA2_NATIVE=1
;或者有一个 ansible.cfg
文件,内容如下:
[defaults]
jinja_native = true
(我谦虚地建议你养成习惯,至少在新项目中);然后从过滤器或查找插件返回“真实”未定义值是小菜一碟:
from jinja2.runtime import Undefined
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
return [Undefined("Don't look up")]
然后,
| default
、
is defined
等将按照您的预期工作,并且您可以自定义错误消息(通过
Undefined
构造函数参数,如上所示),Ansible 将在未定义的值传播到顶级。