diff --git a/changelogs/fragments/allow_lists_of_config_choices.yml b/changelogs/fragments/allow_lists_of_config_choices.yml new file mode 100644 index 00000000000..c5f449532da --- /dev/null +++ b/changelogs/fragments/allow_lists_of_config_choices.yml @@ -0,0 +1,2 @@ +bugfixes: + - plugin config now allows list type options to have multiple valid choices (#74225). diff --git a/lib/ansible/config/manager.py b/lib/ansible/config/manager.py index adc8d78faff..51f6b40be94 100644 --- a/lib/ansible/config/manager.py +++ b/lib/ansible/config/manager.py @@ -552,7 +552,15 @@ class ConfigManager(object): # deal with restricted values if value is not None and 'choices' in defs[config] and defs[config]['choices'] is not None: - if value not in defs[config]['choices']: + invalid_choices = True # assume the worst! + if defs[config].get('type') == 'list': + # for a list type, compare all values in type are allowed + invalid_choices = not all(choice in defs[config]['choices'] for choice in value) + else: + # these should be only the simple data types (string, int, bool, float, etc) .. ignore dicts for now + invalid_choices = value not in defs[config]['choices'] + + if invalid_choices: raise AnsibleOptionsError('Invalid value "%s" for configuration option "%s", valid values are: %s' % (value, to_native(_get_entry(plugin_type, plugin_name, config)), defs[config]['choices'])) diff --git a/test/integration/targets/config/lookup_plugins/bogus.py b/test/integration/targets/config/lookup_plugins/bogus.py new file mode 100644 index 00000000000..34dc98a24c4 --- /dev/null +++ b/test/integration/targets/config/lookup_plugins/bogus.py @@ -0,0 +1,51 @@ +# (c) 2021 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +DOCUMENTATION = """ + name: bogus + author: Ansible Core Team + version_added: histerical + short_description: returns what you gave it + description: + - this is mostly a noop + options: + _terms: + description: stuff to pass through + test_list: + description: does nothihng, just for testing values + type: list + choices: + - Dan + - Yevgeni + - Carla + - Manuela +""" + +EXAMPLES = """ +- name: like some other plugins, this is mostly useless + debug: msg={{ q('bogus', [1,2,3])}} +""" + +RETURN = """ + _list: + description: basically the same as you fed in + type: list + elements: raw +""" + +from ansible.plugins.lookup import LookupBase + + +class LookupModule(LookupBase): + + def run(self, terms, variables=None, **kwargs): + + self.set_options(var_options=variables, direct=kwargs) + dump = self.get_option('test_list') + + return terms diff --git a/test/integration/targets/config/runme.sh b/test/integration/targets/config/runme.sh index ea3989b88bc..bbff6acc3ee 100755 --- a/test/integration/targets/config/runme.sh +++ b/test/integration/targets/config/runme.sh @@ -18,3 +18,6 @@ ANSIBLE_CONFIG=nonexistent.cfg ansible-config dump --only-changed -v | grep 'No # https://github.com/ansible/ansible/pull/73715 ANSIBLE_CONFIG=inline_comment_ansible.cfg ansible-config dump --only-changed | grep "'ansibull'" + +# test the config option validation +ansible-playbook validation.yml "$@" diff --git a/test/integration/targets/config/validation.yml b/test/integration/targets/config/validation.yml new file mode 100644 index 00000000000..1c81e662b6e --- /dev/null +++ b/test/integration/targets/config/validation.yml @@ -0,0 +1,17 @@ +- hosts: localhost + gather_facts: false + tasks: + - name: does nothing but an empty assign, should fail only if lookup gets invalid options + set_fact: whatever={{ lookup('bogus', 1, test_list=['Dan', 'Manuela']) }} + + - name: now pass invalid option and fail! + set_fact: whatever={{ lookup('bogus', 1, test_list=['Dan', 'Manuela', 'Yoko']) }} + register: bad_input + ignore_errors: true + + - name: ensure it fails as expected + assert: + that: + - bad_input is failed + - '"Invalid value " in bad_input.msg' + - '"valid values are:" in bad_input.msg'