When Ansible encounters an if
block, and the if
condition involves the groups
variable, it seems to expand the contents of the block before it has evaluated the if
condition. This is causing an undefined variable error that the if
condition would otherwise protect against.
Why does error occur? Is it expected behaviour, or a bug?
I've reduced the behaviour to a minimal test case.
inventory.yml
group1:
group2:
hosts:
localhost:
vars:
foo: "{{ groups.group1[0] }}"
An empty string, because in both cases the if
condition is false
$ 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": ""
}
When the if
condition involves the groups
variable, foo
is evaluated, resulting in an undefined variable message
$ 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"
}
I'm using Ansible 2.7.9.
Special VariablesAnsible provides a list of predefined variables that can be referenced in Jinja2 templates and playbooks but cannot be altered or defined by the user. Collectively, the list of Ansible predefined variables is referred to as Ansible facts and these are gathered when a playbook is executed.
Variable Scopes Ansible has 3 main scopes: Global: this is set by config, environment variables and the command line. Play: each play and contained structures, vars entries, include_vars, role defaults and vars. Host: variables directly associated to a host, like inventory, facts or registered task outputs.
Variable Name Rules Ansible has a strict set of rules to create valid variable names. Variable names can contain only letters, numbers, and underscores and must start with a letter or underscore. Some strings are reserved for other purposes and aren't valid variable names, such as Python Keywords or Playbook Keywords.
Defined by the variable in use. If we leave one of both empty, Ansible will see those empty as defined but “None” (Python null) as content. With the omit filter we can remove the parameter from the play, so if the parameter is empty it won't be used.
Ansible expands templated keys on demand from the magical context dictionary it passes to Jinja, as Jinja requests them, however Jinja early-binds any name referenced by a template before any processing begins.
Jinja expects the context to yield either a concrete value or the equivalent of KeyError
("Undefined" IIRC), Ansible OTOH uses this moment to recursively invoke Jinja in order to build the value to pass to the original template invocation. It is in this recursive invocation that your error is occurring.
It may be helpful to look at the raw Jinja source for a similar template (prepared with 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'
Notice how the calls to resolve()
complete before any conditional evaluation occurs. It is within resolve()
that Ansible attempts to recursively expand your foo
variable.
It should be possible to tweak things so that foo
is only expanded should Jinja attempt to convert it to a string (or similar), so I'd suggest filing an upstream bug.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With