This commit is contained in:
Brian Coca 2026-05-27 22:08:57 -05:00 committed by GitHub
commit 8fd01e2193
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 29 additions and 14 deletions

View file

@ -46,7 +46,7 @@ def _decode_escapes(s):
return _ESCAPE_SEQUENCE_RE.sub(decode_match, s)
def parse_kv(args, check_raw=False):
def parse_kv(args, check_raw=False, action=None):
"""
Convert a string of key/value items to a dict. If any free-form params
are found and the check_raw option is set to True, they will be added
@ -61,6 +61,9 @@ def parse_kv(args, check_raw=False):
if trusted_tag := TrustedAsTemplate.get_tag(args):
tags.append(trusted_tag)
# avoid circular import
from ansible.plugins.action import get_action_options
args = to_text(args, nonstring='passthru')
options = {}
@ -87,7 +90,7 @@ def parse_kv(args, check_raw=False):
v = x[pos + 1:]
# FIXME: make the retrieval of this list of shell/command options a function, so the list is centralized
if check_raw and k not in ('creates', 'removes', 'chdir', 'executable', 'warn', 'stdin', 'stdin_add_newline', 'strip_empty_ends'):
if check_raw and k not in get_action_options(action):
raw_params.append(orig_x)
else:
options[k.strip()] = unquote(v.strip())
@ -219,7 +222,6 @@ def split_args(args):
params[-1] += ' '
continue
# if we hit a line continuation character, but
# we're not inside quotes, ignore it and continue
# on to the next token while setting a flag
if token == '\\' and not inside_quotes:

View file

@ -42,17 +42,10 @@ PATH_CACHE = {} # type: dict[str, list[_t_loader.PluginPathContext] | None]
PLUGIN_PATH_CACHE = {} # type: dict[str, dict[str, dict[str, _t_loader.PluginPathContext]]]
def get_plugin_class(obj):
if isinstance(obj, str):
return obj.lower().replace('module', '')
else:
return obj.__class__.__name__.lower().replace('module', '')
class _ConfigurablePlugin(t.Protocol):
"""Protocol to provide type-safe access to config for plugin-related mixins."""
def get_option(self, option: str, hostvars: dict[str, object] | None = None) -> t.Any: ...
def get_option(self, option: str, hostvars: dict[str, object] | None = None) -> object: ...
class _AnsiblePluginInfoMixin(_plugin_info.HasPluginInfo):
@ -159,6 +152,10 @@ class AnsiblePlugin(_AnsiblePluginInfoMixin, _ConfigurablePlugin, metaclass=abc.
self.set_options()
return option in self._options
@property
def plugin_type(self):
return self.__class__.__name__.lower().replace('module', '')
@property
def option_definitions(self):
if (not hasattr(self, "_defs")) or self._defs is None:
@ -189,7 +186,7 @@ class AnsibleJinja2Plugin(AnsiblePlugin, metaclass=abc.ABCMeta):
def plugin_type(self) -> str:
...
def _no_options(self, *args, **kwargs) -> t.NoReturn:
def _no_options(self, *args, **kwargs) -> t.Never:
raise NotImplementedError()
has_option = get_option = get_options = option_definitions = set_option = set_options = _no_options

View file

@ -38,6 +38,9 @@ from ansible._internal._templating import _engine
from ansible._internal import _task
from .. import _AnsiblePluginInfoMixin
from ansible.plugins.loader import module_loader
from ansible.cli.doc import DocCLI
display = Display()
if t.TYPE_CHECKING:
@ -50,6 +53,19 @@ if t.TYPE_CHECKING:
VariableLayer = _task.VariableLayer # public API
def get_action_options(action):
# avoid circulairty
if action is None:
# fallback/default hardcoded list from before
options = ('creates', 'removes', 'chdir', 'executable', 'warn', 'stdin', 'stdin_add_newline', 'strip_empty_ends')
else:
doc, *stuff = DocCLI._get_plugin_doc(action, 'module', module_loader, [])
options = doc.get('options', {}).keys()
return options
def _validate_utf8_json(d):
if isinstance(d, str):
# Purposefully not using to_bytes here for performance reasons

View file

@ -438,7 +438,7 @@ class PluginLoader:
for i in paths:
if i not in ret:
ret.append(i)
return os.pathsep.join(ret)
return to_text(os.pathsep.join(ret), errors='surrogate_or_strict')
def print_paths(self):
return self.format_paths(self._get_paths(subdirs=False))
@ -531,7 +531,7 @@ class PluginLoader:
# plugins w/o class name don't support config
if self.class_name:
type_name = get_plugin_class(self.class_name)
type_name = self.class_name.lower().replace('module', '')
# if type name != 'module_doc_fragment':
if type_name in C.CONFIGURABLE_PLUGINS and not C.config.has_configuration_definition(type_name, name):