diff --git a/changelogs/fragments/fix_errors.yml b/changelogs/fragments/fix_errors.yml new file mode 100644 index 00000000000..995cc28ffda --- /dev/null +++ b/changelogs/fragments/fix_errors.yml @@ -0,0 +1,2 @@ +bugfixes: + - Errors now preserve stacked error messages even when YAML is involved. diff --git a/lib/ansible/errors/__init__.py b/lib/ansible/errors/__init__.py index f003b589c8a..78853757f8a 100644 --- a/lib/ansible/errors/__init__.py +++ b/lib/ansible/errors/__init__.py @@ -66,14 +66,18 @@ class AnsibleError(Exception): from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject message = [self._message] + + # Add from previous exceptions + if self.orig_exc: + message.append('. %s' % to_native(self.orig_exc)) + + # Add from yaml to give specific file/line no if isinstance(self.obj, AnsibleBaseYAMLObject): extended_error = self._get_extended_error() if extended_error and not self._suppress_extended_error: message.append( '\n\n%s' % to_native(extended_error) ) - elif self.orig_exc: - message.append('. %s' % to_native(self.orig_exc)) return ''.join(message) diff --git a/lib/ansible/playbook/base.py b/lib/ansible/playbook/base.py index f93fb5ef5f9..46483efda2d 100644 --- a/lib/ansible/playbook/base.py +++ b/lib/ansible/playbook/base.py @@ -19,7 +19,7 @@ from ansible import context from ansible.errors import AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleAssertionError from ansible.module_utils.six import string_types from ansible.module_utils.parsing.convert_bool import boolean -from ansible.module_utils.common.text.converters import to_text, to_native +from ansible.module_utils.common.text.converters import to_text from ansible.parsing.dataloader import DataLoader from ansible.playbook.attribute import Attribute, FieldAttribute, ConnectionFieldAttribute, NonInheritableFieldAttribute from ansible.plugins.loader import module_loader, action_loader @@ -567,14 +567,14 @@ class FieldAttributeBase: setattr(self, name, value) except (TypeError, ValueError) as e: value = getattr(self, name) - raise AnsibleParserError("the field '%s' has an invalid value (%s), and could not be converted to %s. " - "The error was: %s" % (name, value, attribute.isa, e), obj=self.get_ds(), orig_exc=e) + raise AnsibleParserError(f"the field '{name}' has an invalid value ({value!r}), and could not be converted to {attribute.isa}.", + obj=self.get_ds(), orig_exc=e) except (AnsibleUndefinedVariable, UndefinedError) as e: if templar._fail_on_undefined_errors and name != 'name': if name == 'args': - msg = "The task includes an option with an undefined variable. The error was: %s" % (to_native(e)) + msg = "The task includes an option with an undefined variable." else: - msg = "The field '%s' has an invalid value, which includes an undefined variable. The error was: %s" % (name, to_native(e)) + msg = f"The field '{name}' has an invalid value, which includes an undefined variable." raise AnsibleParserError(msg, obj=self.get_ds(), orig_exc=e) self._finalized = True diff --git a/test/integration/targets/template/tasks/main.yml b/test/integration/targets/template/tasks/main.yml index d96941fd32d..8a45494033f 100644 --- a/test/integration/targets/template/tasks/main.yml +++ b/test/integration/targets/template/tasks/main.yml @@ -714,7 +714,9 @@ - name: check that proper error message is emitted when in operator is used assert: - that: "\"The error was: 'y' is undefined\n\n\" in error.msg" + that: + - '"The task includes an option with an undefined variable" in error.msg' + - "\"'y' is undefined\n\n\" in error.msg" - template: src: template_import_macro_globals.j2