为什么 Ansible 即使在 false if 块中也要计算这个变量?



当 Ansible 遇到if块,并且if条件涉及groups变量时,它似乎在评估if条件之前就扩展了块的内容。这会导致未定义的变量错误,否则if条件会防止该错误。

为什么会发生错误?是预期行为,还是错误?

我已将行为简化为最小的测试用例。

库存.yml

group1:
group2:
hosts:
localhost:
vars:
foo: "{{ groups.group1[0] }}"

预期

空字符串,因为在这两种情况下if条件都是假

$ ansible -i inventory.yml group2 -mdebug -amsg="{% if false %}{{ foo }}{% endif %}"
localhost | SUCCESS => {
"msg": ""
}
$ ansible -i inventory.yml group2 -mdebug -amsg="{% if groups.group1 %}{{ foo }}{% endif %}"
localhost | SUCCESS => {
"msg": ""
}

实际

if条件涉及groups变量时,将计算foo,从而生成未定义的变量消息

$ ansible -i inventory.yml group2 -mdebug -amsg="{% if false %}{{ foo }}{% endif %}"
localhost | SUCCESS => {
"msg": ""
}
$ ansible -i inventory.yml group2 -mdebug -amsg="{% if groups.group1 %}{{ foo }}{% endif %}"
localhost | FAILED! => {
"msg": "The task includes an option with an undefined variable. The error was: list object has no element 0"
}

我正在使用 Ansible 2.7.9。

Ansible 根据需要从传递给 Jinja 的神奇上下文字典中扩展模板化密钥,因为 Jinja 请求它们,但是 Jinja 会在任何处理开始之前提前绑定模板引用的任何名称。

Jinja 希望上下文产生一个具体的值或等价于KeyError("未定义的" IIRC),Ansible OTOH 利用这一时刻递归调用 Jinja,以便构建要传递给原始模板调用的值。正是在此递归调用中发生了错误。

查看类似模板的原始 Jinja 源(使用jinja2.Environment().compile(..., raw=True)准备)可能会有所帮助:

from __future__ import division
from jinja2.runtime import LoopContext, TemplateReference, Macro, Markup, TemplateRuntimeError, missing, concat, escape, markup_join, unicode_join, to_string, identity, TemplateNotFound, Namespace
name = None
def root(context, missing=missing, environment=environment):
resolve = context.resolve_or_missing
undefined = environment.undefined
if 0: yield None
l_0_foo = resolve('foo')
l_0_groups = resolve('groups')
pass
if environment.getattr((undefined(name='groups') if l_0_groups is missing else l_0_groups), 'group1'):
pass
yield to_string((undefined(name='foo') if l_0_foo is missing else l_0_foo))
blocks = {}
debug_info = '1=12'

请注意,在发生任何条件评估之前,对resolve()调用是如何完成的。Ansible 尝试递归扩展您的foo变量resolve()

应该可以调整内容,以便仅在 Jinja 尝试将其转换为字符串(或类似字符串)时才扩展foo,所以我建议提交上游错误。

Ansilbe 与 Jinja2 结合使用将首先将模板化的 var 评估为字符串,然后再有效地播放条件,从而导致失败。

ansible/jinja2 管理案例的方法是在定义foo时使用default过滤器

foo: "{{ groups.group1[0] | default('') }}"

这样,您无需测试任何其他间接值。只需使用 var

ansible -i inventory.yml group2 -mdebug -amsg="{{ foo }}"
localhost | SUCCESS => {
"msg": ""
}

似乎groups.group1[0]是没有定义的。

最新更新