From 6065638e008468fa87db0b4f6900acb23da39760 Mon Sep 17 00:00:00 2001 From: Sam Doran Date: Tue, 9 Jun 2020 13:05:53 -0400 Subject: [PATCH] Display parameter name in string conversion warning (#57145) Add prefix to error message for nested options. This is helpful if a subelement key has the same name as a top level key. --- ...conversion-warning-add-parameter-name.yaml | 2 + lib/ansible/module_utils/basic.py | 40 +++++++++++++++---- 2 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 changelogs/fragments/string-conversion-warning-add-parameter-name.yaml diff --git a/changelogs/fragments/string-conversion-warning-add-parameter-name.yaml b/changelogs/fragments/string-conversion-warning-add-parameter-name.yaml new file mode 100644 index 00000000000..60577185f87 --- /dev/null +++ b/changelogs/fragments/string-conversion-warning-add-parameter-name.yaml @@ -0,0 +1,2 @@ +bugfixes: + - add parameter name to warning message when values are converted to strings (https://github.com/ansible/ansible/pull/57145) diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index 697e452e5fe..e1569da4c97 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -1613,7 +1613,7 @@ class AnsibleModule(object): def safe_eval(self, value, locals=None, include_exceptions=False): return safe_eval(value, locals, include_exceptions) - def _check_type_str(self, value): + def _check_type_str(self, value, param=None, prefix=''): opts = { 'error': False, 'warn': False, @@ -1626,12 +1626,22 @@ class AnsibleModule(object): return check_type_str(value, allow_conversion) except TypeError: common_msg = 'quote the entire value to ensure it does not change.' + from_msg = '{0!r}'.format(value) + to_msg = '{0!r}'.format(to_text(value)) + + if param is not None: + if prefix: + param = '{0}{1}'.format(prefix, param) + + from_msg = '{0}: {1!r}'.format(param, value) + to_msg = '{0}: {1!r}'.format(param, to_text(value)) + if self._string_conversion_action == 'error': msg = common_msg.capitalize() raise TypeError(to_native(msg)) elif self._string_conversion_action == 'warn': - msg = ('The value {0!r} (type {0.__class__.__name__}) in a string field was converted to {1!r} (type string). ' - 'If this does not look like what you expect, {2}').format(value, to_text(value), common_msg) + msg = ('The value "{0}" (type {1.__class__.__name__}) was converted to "{2}" (type string). ' + 'If this does not look like what you expect, {3}').format(from_msg, value, to_msg, common_msg) self.warn(to_native(msg)) return to_native(value, errors='surrogate_or_strict') @@ -1716,7 +1726,7 @@ class AnsibleModule(object): if not self.bypass_checks: self._check_required_arguments(spec, param) - self._check_argument_types(spec, param) + self._check_argument_types(spec, param, new_prefix) self._check_argument_values(spec, param) self._check_required_together(v.get('required_together', None), param) @@ -1751,9 +1761,16 @@ class AnsibleModule(object): def _handle_elements(self, wanted, param, values): type_checker, wanted_name = self._get_wanted_type(wanted, param) validated_params = [] + # Get param name for strings so we can later display this value in a useful error message if needed + kwargs = {} + if wanted_name == 'str': + if isinstance(param, string_types): + kwargs['param'] = param + elif isinstance(param, dict): + kwargs['param'] = list(param.keys())[0] for value in values: try: - validated_params.append(type_checker(value)) + validated_params.append(type_checker(value, **kwargs)) except (TypeError, ValueError) as e: msg = "Elements value for option %s" % param if self._options_context: @@ -1762,7 +1779,7 @@ class AnsibleModule(object): self.fail_json(msg=msg) return validated_params - def _check_argument_types(self, spec=None, param=None): + def _check_argument_types(self, spec=None, param=None, prefix=''): ''' ensure all arguments have the requested type ''' if spec is None: @@ -1780,8 +1797,17 @@ class AnsibleModule(object): continue type_checker, wanted_name = self._get_wanted_type(wanted, k) + # Get param name for strings so we can later display this value in a useful error message if needed + kwargs = {} + if wanted_name == 'str': + kwargs['param'] = list(param.keys())[0] + + # Get the name of the parent key if this is a nested option + if prefix: + kwargs['prefix'] = prefix + try: - param[k] = type_checker(value) + param[k] = type_checker(value, **kwargs) wanted_elements = v.get('elements', None) if wanted_elements: if wanted != 'list' or not isinstance(param[k], list):