diff --git a/changelogs/fragments/75073-role-argspec-suboption-variables.yaml b/changelogs/fragments/75073-role-argspec-suboption-variables.yaml new file mode 100644 index 00000000000..eca9aad78de --- /dev/null +++ b/changelogs/fragments/75073-role-argspec-suboption-variables.yaml @@ -0,0 +1,2 @@ +bugfixes: + - roles - make sure argspec validation task templates suboptions (https://github.com/ansible/ansible/issues/75070). diff --git a/lib/ansible/plugins/action/validate_argument_spec.py b/lib/ansible/plugins/action/validate_argument_spec.py index 4162005202c..9ad0a46df65 100644 --- a/lib/ansible/plugins/action/validate_argument_spec.py +++ b/lib/ansible/plugins/action/validate_argument_spec.py @@ -31,12 +31,8 @@ class ActionModule(ActionBase): for argument_name, argument_attrs in iteritems(argument_spec): if argument_name in task_vars: - if isinstance(task_vars[argument_name], string_types): - value = self._templar.do_template(task_vars[argument_name]) - if value: - args[argument_name] = value - else: - args[argument_name] = task_vars[argument_name] + args[argument_name] = task_vars[argument_name] + args = self._templar.template(args) return args def run(self, tmp=None, task_vars=None): diff --git a/test/integration/targets/roles_arg_spec/test.yml b/test/integration/targets/roles_arg_spec/test.yml index e7c1fae054b..06268c6a527 100644 --- a/test/integration/targets/roles_arg_spec/test.yml +++ b/test/integration/targets/roles_arg_spec/test.yml @@ -303,3 +303,38 @@ - ansible_failed_result.validate_args_context.name == "blah" - ansible_failed_result.validate_args_context.type == "role" - "ansible_failed_result.validate_args_context.path is search('roles_arg_spec/collections/ansible_collections/foo/bar/roles/blah')" + +- name: "New play to reset vars: Test templating succeeds" + hosts: localhost + gather_facts: false + vars: + value_some_choices: "choice2" + value_some_list: [1.5] + value_some_dict: {"some_key": "some_value"} + value_some_int: 1 + value_some_float: 1.5 + value_some_json: '{[1, 3, 3] 345345|45v<#!}' + value_some_jsonarg: {"foo": [1, 3, 3]} + value_some_second_level: True + value_third_level: 5 + tasks: + - block: + - include_role: + name: test1 + vars: + some_choices: "{{ value_some_choices }}" + some_list: "{{ value_some_list }}" + some_dict: "{{ value_some_dict }}" + some_int: "{{ value_some_int }}" + some_float: "{{ value_some_float }}" + some_json: "{{ value_some_json }}" + some_jsonarg: "{{ value_some_jsonarg }}" + some_dict_options: + some_second_level: "{{ value_some_second_level }}" + multi_level_option: + second_level: + third_level: "{{ value_third_level }}" + rescue: + - debug: var=ansible_failed_result + - fail: + msg: "Should not get here" diff --git a/test/integration/targets/roles_arg_spec/test_complex_role_fails.yml b/test/integration/targets/roles_arg_spec/test_complex_role_fails.yml index 35ae3877b72..a04785fb121 100644 --- a/test/integration/targets/roles_arg_spec/test_complex_role_fails.yml +++ b/test/integration/targets/roles_arg_spec/test_complex_role_fails.yml @@ -3,13 +3,23 @@ hosts: localhost gather_facts: false vars: + ansible_unicode_type_match: "" unicode_type_match: "" string_type_match: "" float_type_match: "" + list_type_match: "" + ansible_list_type_match: "" + dict_type_match: "" + ansible_dict_type_match: "" + ansible_unicode_class_match: "" unicode_class_match: "" string_class_match: "" bytes_class_match: "" float_class_match: "" + list_class_match: "" + ansible_list_class_match: "" + dict_class_match: "" + ansible_dict_class_match: "" expected: test1_1: argument_errors: [ @@ -98,24 +108,44 @@ # because py3 quotes the value in the error while py2 does not, so we just ignore # the rest of the line. actual_generic: "{{ ansible_failed_result.argument_errors| + map('replace', ansible_unicode_type_match, 'STR')| map('replace', unicode_type_match, 'STR')| map('replace', string_type_match, 'STR')| map('replace', float_type_match, 'FLOAT')| + map('replace', list_type_match, 'LIST')| + map('replace', ansible_list_type_match, 'LIST')| + map('replace', dict_type_match, 'DICT')| + map('replace', ansible_dict_type_match, 'DICT')| + map('replace', ansible_unicode_class_match, 'STR')| map('replace', unicode_class_match, 'STR')| map('replace', string_class_match, 'STR')| map('replace', bytes_class_match, 'STR')| map('replace', float_class_match, 'FLOAT')| + map('replace', list_class_match, 'LIST')| + map('replace', ansible_list_class_match, 'LIST')| + map('replace', dict_class_match, 'DICT')| + map('replace', ansible_dict_class_match, 'DICT')| map('regex_replace', '''float:.*$''', 'THE_FLOAT_REPR')| map('regex_replace', 'Valid booleans include.*$', '')| list }}" expected_generic: "{{ expected.test1_1.argument_errors| + map('replace', ansible_unicode_type_match, 'STR')| map('replace', unicode_type_match, 'STR')| map('replace', string_type_match, 'STR')| map('replace', float_type_match, 'FLOAT')| + map('replace', list_type_match, 'LIST')| + map('replace', ansible_list_type_match, 'LIST')| + map('replace', dict_type_match, 'DICT')| + map('replace', ansible_dict_type_match, 'DICT')| + map('replace', ansible_unicode_class_match, 'STR')| map('replace', unicode_class_match, 'STR')| map('replace', string_class_match, 'STR')| map('replace', bytes_class_match, 'STR')| map('replace', float_class_match, 'FLOAT')| + map('replace', list_class_match, 'LIST')| + map('replace', ansible_list_class_match, 'LIST')| + map('replace', dict_class_match, 'DICT')| + map('replace', ansible_dict_class_match, 'DICT')| map('regex_replace', '''float:.*$''', 'THE_FLOAT_REPR')| map('regex_replace', 'Valid booleans include.*$', '')| list }}"