Ansible: "default" and "bool" filters
2015-11-30There are plenty of bool and default filters usage in Ansible playbooks and templates. For example, in debops/ansible-docker: when: docker_upstream| d() | bool.
Where docker_upstream is an YAML boolean: docker_upstream: False. It seems like “when” condition is overbloated again :)
The var | d() | bool construction is spread all over the place, for example, in docker config template:
{% if docker_listen | d() | bool %}
Where docker_listen is an YAML list: docker_listen: [ '{{ docker_tcp_listen }}' ].
Is there a more simpler way to express things? Maybe when: docker_upstream or %{ if docker_listen %} will be enough?
First of all, d is an alias for default, and default filter is defined in jinja2/filters.py:
def do_default(value, default_value=u'', boolean=False):
"""If the value is undefined it will return the passed default value,
otherwise the value of the variable:
.. sourcecode:: jinja
{{ my_variable|default('my_variable is not defined') }}
This will output the value of ``my_variable`` if the variable was
defined, otherwise ``'my_variable is not defined'``. If you want
to use default with variables that evaluate to false you have to
set the second parameter to `true`:
.. sourcecode:: jinja
{{ ''|default('the string was empty', true) }}
"""
if isinstance(value, Undefined) or (boolean and not value):
return default_value
return valueSo, the default “default value” is an empty string.
“var | d()” test
With default filter:
from jinja2 import Template
tmpl = Template('{{ var | d() }}')
>>> print tmpl.render()
# An empty string
>>> print tmpl.render(var=None)
None
>>> print tmpl.render(var='')
# An empty string
>>> print tmpl.render(var='abc')
abc
>>> print tmpl.render(var=' ')
# A space
>>> print tmpl.render(var=[])
[]
>>> print tmpl.render(var=['list'])
['list']
>>> print tmpl.render(var={})
{}
>>> print tmpl.render(var={'key': 'value'})
{'key': 'value'}Without default filter:
from jinja2 import Template
tmpl = Template('{{ var }}')
>>> print tmpl.render()
# An empty string
>>> print tmpl.render(var=None)
None
>>> print tmpl.render(var='')
# An empty string
>>> print tmpl.render(var='abc')
abc
>>> print tmpl.render(var=' ')
# A space
>>> print tmpl.render(var=[])
[]
>>> print tmpl.render(var=['list'])
['list']
>>> print tmpl.render(var={})
{}
>>> print tmpl.render(var={'key': 'value'})
{'key': 'value'}Pretty same, eh?
“str | d() | bool” test
With default filter:
from jinja2 import Template
from ansible.runner.filter_plugins.core import bool
Template('').environment.filters['bool'] = bool
tmpl = Template('{{ var | d() | bool }}')
>>> print tmpl.render()
False
>>> print tmpl.render(var=None)
None
>>> print tmpl.render(var='')
False
>>> print tmpl.render(var='abc')
False
>>> print tmpl.render(var=' ')
False
>>> print tmpl.render(var='True')
True
>>> print tmpl.render(var=[])
False
>>> print tmpl.render(var=['list'])
False
>>> print tmpl.render(var={})
False
>>> print tmpl.render(var={'key': 'value'})
FalseWithout default filter:
from jinja2 import Template
from ansible.runner.filter_plugins.core import bool
Template('').environment.filters['bool'] = bool
tmpl = Template('{{ var | bool }}')
>>> print tmpl.render()
False
>>> print tmpl.render(var=None)
None
>>> print tmpl.render(var='')
False
>>> print tmpl.render(var='abc')
False
>>> print tmpl.render(var=' ')
False
>>> print tmpl.render(var='True')
True
>>> print tmpl.render(var=[])
False
>>> print tmpl.render(var=['list'])
False
>>> print tmpl.render(var={})
False
>>> print tmpl.render(var={'key': 'value'})
FalsePretty same again. So, it seems that default() or d() usage in conditions has no sense at all.
There are two specific features:
- When using undefined variables in actual Ansible playbooks without
defaultfilterAnsibleUndefinedVariablewill be thrown. So finally, you have to usedefaultfilter to be safe. - Also, mention that
boolfilter for empty and non-empty lists and dicts evaluates toFalse. It’s followed from sources (see below).
Therefore, what about bool filter?
Remember when: docker_upstream | d() | bool, where docker_upstream is an YAML boolean.
Or {% if docker_listen | d() | bool %}, where docker_listen is an YAML list.
The bool filter is the part of Ansible plugins:
def bool(a):
''' return a bool for the arg '''
if a is None or type(a) == bool:
return a
if type(a) in types.StringTypes:
a = a.lower()
if a in ['yes', 'on', '1', 'true', 1]:
return True
else:
return FalseYou can clearly see from bool function implementation that {% if docker_listen | d() | bool %} results in True only if docker_listen: True. If docker_listen is an arbitrary string or a list (as expected) {% if docker_listen | d() | bool %} results in False always.
Therefore {% if docker_listen | d() %} gives us a correct behaviour for strings, lists and dictionaries (mappings). The reason is explained here (see “Secondly”, “Third” and “More detailed” quotes):
The if statement in Jinja is comparable with the Python if statement. In the simplest form, you can use it to test if a variable is defined, not empty or not false.
For sequences, (strings, lists, tuples), use the fact that empty sequences are false.
The following values are considered false: any empty mapping, for example,
{}.
But for booleans you have to use bool filter — when: docker_upstream | d() | bool, because when: docker_upstream | d() results in True even if docker_upstream is False.
Links: