Fix plugin set option (#82300)

* plugin config, ensure set_option correctly casts

  until now we relied on plugin author getting the right type, now
  the config system itself will process as it would for set_options
pull/81918/head
Brian Coca 5 months ago committed by GitHub
parent 5346009d2c
commit 4479c9df13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,2 @@
bugfixes:
- set_option method for plugins to update config now properly passes through type casting and validation.

@ -91,7 +91,7 @@ class AnsiblePlugin(ABC):
return options
def set_option(self, option, value):
self._options[option] = value
self._options[option] = C.config.get_config_value(option, plugin_type=self.plugin_type, plugin_name=self._load_name, direct={option: value})
def set_options(self, task_keys=None, var_options=None, direct=None):
'''
@ -106,7 +106,8 @@ class AnsiblePlugin(ABC):
# allow extras/wildcards from vars that are not directly consumed in configuration
# this is needed to support things like winrm that can have extended protocol options we don't directly handle
if self.allow_extras and var_options and '_extras' in var_options:
self.set_option('_extras', var_options['_extras'])
# these are largely unvalidated passthroughs, either plugin or underlying API will validate
self._options['_extras'] = var_options['_extras']
def has_option(self, option):
if not self._options:

@ -165,7 +165,7 @@ class CallbackBase(AnsiblePlugin):
_copy_result = deepcopy
def set_option(self, k, v):
self._plugin_options[k] = v
self._plugin_options[k] = C.config.get_config_value(k, plugin_type=self.plugin_type, plugin_name=self._load_name, direct={k: v})
def get_option(self, k):
return self._plugin_options[k]

@ -0,0 +1,59 @@
# (c) 2021 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import annotations
DOCUMENTATION = """
name: casting
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
test_int:
description: does nothihng, just to test casting
type: int
test_bool:
description: does nothihng, just to test casting
type: bool
test_str:
description: does nothihng, just to test casting
type: str
"""
EXAMPLES = """
- name: like some other plugins, this is mostly useless
debug: msg={{ q('casting', [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)
for cast in (list, int, bool, str):
option = 'test_%s' % str(cast).replace("<class '", '').replace("'>", '')
value = self.get_option(option)
if value is None or type(value) is cast:
continue
raise Exception('%s is not a %s: got %s/%s' % (option, cast, type(value), value))
return terms

@ -0,0 +1,58 @@
# (c) 2021 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import annotations
DOCUMENTATION = """
name: casting_individual
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
test_int:
description: does nothihng, just to test casting
type: int
test_bool:
description: does nothihng, just to test casting
type: bool
test_str:
description: does nothihng, just to test casting
type: str
"""
EXAMPLES = """
- name: like some other plugins, this is mostly useless
debug: msg={{ q('casting_individual', [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):
for cast in (list, int, bool, str):
option = 'test_%s' % str(cast).replace("<class '", '').replace("'>", '')
if option in kwargs:
self.set_option(option, kwargs[option])
value = self.get_option(option)
if type(value) is not cast:
raise Exception('%s is not a %s: got %s/%s' % (option, cast, type(value), value))
return terms

@ -15,3 +15,10 @@
- bad_input is failed
- '"Invalid value " in bad_input.msg'
- '"valid values are:" in bad_input.msg'
- name: test config option casting
set_fact:
direct: "{{ lookup('casting', 1, test_list=[1,2,3], test_int=1, test_bool=True, test_str='lola') }}"
from_strings: "{{ lookup('casting', 1, test_list='1,2,3', test_int='1', test_bool='true', test_str='lola') }}"
direct_individual: "{{ lookup('casting_individual', 1, test_list='1,2,3', test_int='1', test_bool='true', test_str='lola') }}"
from_strings_individual: "{{ lookup('casting_individual', 1, test_list='1,2,3', test_int='1', test_bool='true', test_str='lola') }}"

@ -103,6 +103,8 @@ test/integration/targets/ansible-test-docker/ansible_collections/ns/col/tests/un
test/integration/targets/ansible-test-no-tty/ansible_collections/ns/col/vendored_pty.py pep8!skip # vendored code
test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/modules/my_module.py pylint:relative-beyond-top-level
test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/my_util2.py pylint:relative-beyond-top-level
test/integration/targets/config/lookup_plugins/casting.py pylint:unidiomatic-typecheck
test/integration/targets/config/lookup_plugins/casting_individual.py pylint:unidiomatic-typecheck
test/integration/targets/fork_safe_stdio/vendored_pty.py pep8!skip # vendored code
test/integration/targets/gathering_facts/library/bogus_facts shebang
test/integration/targets/gathering_facts/library/dummy1 shebang

Loading…
Cancel
Save