fix combine filter using undefined vars (#55840)

* Check variables are defined before using combine filter

* Add tests for the combine filter

* Remove dependencies that should already be installed

* relocate the function to recursively check for undefined vars

add another test

* changelog
pull/55940/head
Sloane Hertel 5 years ago committed by Brian Coca
parent c4004b5fb1
commit 11279a909d

@ -0,0 +1,2 @@
bugfixes:
- combine filter - Validate that undefined variables aren't used (https://github.com/ansible/ansible/issues/55810).

@ -48,6 +48,7 @@ from ansible.module_utils.common.collections import is_sequence
from ansible.module_utils.common._collections_compat import Mapping, MutableMapping
from ansible.parsing.ajson import AnsibleJSONEncoder
from ansible.parsing.yaml.dumper import AnsibleDumper
from ansible.template import recursive_check_defined
from ansible.utils.display import Display
from ansible.utils.encrypt import passlib_or_crypt
from ansible.utils.hashing import md5s, checksum_s
@ -300,8 +301,10 @@ def combine(*terms, **kwargs):
dicts = []
for t in terms:
if isinstance(t, MutableMapping):
recursive_check_defined(t)
dicts.append(t)
elif isinstance(t, list):
recursive_check_defined(t)
dicts.append(combine(*t, **kwargs))
else:
raise AnsibleFilterError("|combine expects dictionaries, got " + repr(t))

@ -178,6 +178,20 @@ def _count_newlines_from_end(in_str):
return i
def recursive_check_defined(item):
from jinja2.runtime import Undefined
if isinstance(item, MutableMapping):
for key in item:
recursive_check_defined(item[key])
elif isinstance(item, list):
for i in item:
recursive_check_defined(i)
else:
if isinstance(item, Undefined):
raise AnsibleFilterError("{0} is undefined".format(item))
class AnsibleUndefined(StrictUndefined):
'''
A custom Undefined class, which returns further Undefined objects on access,

@ -277,3 +277,37 @@
loop: "{{ hostvars|dict2items }}"
loop_control:
label: "{{ item.key }}"
- name: Ensure combining two dictionaries containing undefined variables provides a helpful error
block:
- set_fact:
foo:
key1: value1
- set_fact:
combined: "{{ foo | combine({'key2': undef_variable}) }}"
ignore_errors: yes
register: result
- assert:
that:
- "result.msg.startswith('The task includes an option with an undefined variable')"
- set_fact:
combined: "{{ foo | combine({'key2': {'nested': [undef_variable]}})}}"
ignore_errors: yes
register: result
- assert:
that:
- "result.msg.startswith('The task includes an option with an undefined variable')"
- set_fact:
key2: is_defined
- set_fact:
combined: "{{ foo | combine({'key2': key2}) }}"
- assert:
that:
- "combined.key2 == 'is_defined'"

Loading…
Cancel
Save