summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/ansible/playbook/conditional.py51
-rw-r--r--lib/ansible/vars/hostvars.py2
2 files changed, 43 insertions, 10 deletions
diff --git a/lib/ansible/playbook/conditional.py b/lib/ansible/playbook/conditional.py
index e0c426df3b..958fe29de0 100644
--- a/lib/ansible/playbook/conditional.py
+++ b/lib/ansible/playbook/conditional.py
@@ -19,6 +19,8 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
+import re
+
from jinja2.exceptions import UndefinedError
from ansible.compat.six import text_type
@@ -27,6 +29,8 @@ from ansible.playbook.attribute import FieldAttribute
from ansible.template import Templar
from ansible.module_utils._text import to_native
+DEFINED_REGEX = re.compile(r'(hostvars\[.+\]|[\w_]+)\s+(not\s+is|is|is\s+not)\s+(defined|undefined)')
+
class Conditional:
'''
@@ -62,6 +66,18 @@ class Conditional:
when = self._get_parent_attribute('when', extend=True, prepend=True)
return when
+ def extract_defined_undefined(self, conditional):
+ results = []
+
+ cond = conditional
+ m = DEFINED_REGEX.search(cond)
+ while m:
+ results.append(m.groups())
+ cond = cond[m.end():]
+ m = DEFINED_REGEX.search(cond)
+
+ return results
+
def evaluate_conditional(self, templar, all_vars):
'''
Loops through the conditionals set on this object, returning
@@ -121,14 +137,31 @@ class Conditional:
else:
raise AnsibleError("unable to evaluate conditional: %s" % original)
except (AnsibleUndefinedVariable, UndefinedError) as e:
- # the templating failed, meaning most likely a
- # variable was undefined. If we happened to be
- # looking for an undefined variable, return True,
- # otherwise fail
- if "is undefined" in original or "is not defined" in original or "not is defined" in original:
- return True
- elif "is defined" in original or "is not undefined" in original or "not is undefined" in original:
- return False
- else:
+ # the templating failed, meaning most likely a variable was undefined. If we happened to be
+ # looking for an undefined variable, return True, otherwise fail
+ try:
+ # first we extract the variable name from the error message
+ var_name = re.compile(r"'(hostvars\[.+\]|[\w_]+)' is undefined").search(str(e)).groups()[0]
+ # next we extract all defined/undefined tests from the conditional string
+ def_undef = self.extract_defined_undefined(conditional)
+ # then we loop through these, comparing the error variable name against
+ # each def/undef test we found above. If there is a match, we determine
+ # whether the logic/state mean the variable should exist or not and return
+ # the corresponding True/False
+ for (du_var, logic, state) in def_undef:
+ # the should exist is a xor test between a negation in the logic portion
+ # against the state (defined or undefined)
+ should_exist = ('not' in logic) != (state == 'defined')
+ # when we compare the var names, normalize quotes because something
+ # like hostvars['foo'] may be tested against hostvars["foo"]
+ if var_name.replace("'", '"') == du_var.replace("'", '"'):
+ if should_exist:
+ return False
+ else:
+ return True
+ # as nothing above matched the failed var name, re-raise here to
+ # trigger the AnsibleUndefinedVariable exception again below
+ raise
+ except Exception as new_e:
raise AnsibleUndefinedVariable("error while evaluating conditional (%s): %s" % (original, e))
diff --git a/lib/ansible/vars/hostvars.py b/lib/ansible/vars/hostvars.py
index 74262b2ed4..3922b827a7 100644
--- a/lib/ansible/vars/hostvars.py
+++ b/lib/ansible/vars/hostvars.py
@@ -73,7 +73,7 @@ class HostVars(collections.Mapping):
'''
host = self._find_host(host_name)
if host is None:
- raise UndefinedError("%s not found in hostvars" % host_name)
+ raise UndefinedError("'hostvars[\"%s\"]' is undefined" % host_name)
return self._variable_manager.get_vars(loader=self._loader, host=host, include_hostvars=False)