From b14eadf55c4bd92f536cdc634dd5fa39ae096d0e Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Tue, 21 Jun 2022 15:41:52 -0400 Subject: [PATCH 1/5] [poc] dynamic raw list --- lib/ansible/parsing/splitter.py | 8 +++++--- lib/ansible/plugins/action/__init__.py | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/ansible/parsing/splitter.py b/lib/ansible/parsing/splitter.py index 18ef976496e..2f2be4228d9 100644 --- a/lib/ansible/parsing/splitter.py +++ b/lib/ansible/parsing/splitter.py @@ -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: diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py index f36c0a091fa..b00b9d35a40 100644 --- a/lib/ansible/plugins/action/__init__.py +++ b/lib/ansible/plugins/action/__init__.py @@ -41,6 +41,9 @@ from ansible._internal._templating import _engine from .. import _AnsiblePluginInfoMixin +from ansible.plugins.loader import module_loader +from ansible.cli.doc import DocCLI + display = Display() if t.TYPE_CHECKING: @@ -51,6 +54,19 @@ if t.TYPE_CHECKING: from ansible.template import Templar +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, text_type): # Purposefully not using to_bytes here for performance reasons From 509e5a5c9a18f3bb35a07e87c5bfb38a841a9965 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Wed, 22 Jun 2022 10:19:50 -0400 Subject: [PATCH 2/5] only one print_paths --- lib/ansible/plugins/loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ansible/plugins/loader.py b/lib/ansible/plugins/loader.py index a30633e4c0e..4344c28c703 100644 --- a/lib/ansible/plugins/loader.py +++ b/lib/ansible/plugins/loader.py @@ -407,7 +407,7 @@ class PluginLoader: ret = [] for i in paths: if i not in ret: - ret.append(i) + ret.append(to_text(i, errors='surrogate_or_strict')) return os.pathsep.join(ret) def print_paths(self): From 2b4c46c33468e5506f4d5d457c84f7f7b2f6c229 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Wed, 22 Jun 2022 10:41:08 -0400 Subject: [PATCH 3/5] moved plugin_typre res from function to attribute --- lib/ansible/plugins/__init__.py | 11 ++--------- lib/ansible/plugins/loader.py | 2 +- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/lib/ansible/plugins/__init__.py b/lib/ansible/plugins/__init__.py index 833f18e34e6..7d5f11ab101 100644 --- a/lib/ansible/plugins/__init__.py +++ b/lib/ansible/plugins/__init__.py @@ -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): @@ -181,7 +174,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 diff --git a/lib/ansible/plugins/loader.py b/lib/ansible/plugins/loader.py index 4344c28c703..6921ac8f877 100644 --- a/lib/ansible/plugins/loader.py +++ b/lib/ansible/plugins/loader.py @@ -501,7 +501,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): From 4b08b42f4181b081a50721e0ce69da7f57893e94 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Wed, 22 Jun 2022 10:42:24 -0400 Subject: [PATCH 4/5] sws --- lib/ansible/plugins/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/ansible/plugins/__init__.py b/lib/ansible/plugins/__init__.py index 7d5f11ab101..a5dc143d708 100644 --- a/lib/ansible/plugins/__init__.py +++ b/lib/ansible/plugins/__init__.py @@ -144,6 +144,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: From e07afcc9284d10782185df129d3d34ee777449f2 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Wed, 22 Jun 2022 10:48:12 -0400 Subject: [PATCH 5/5] one conversion --- lib/ansible/plugins/loader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ansible/plugins/loader.py b/lib/ansible/plugins/loader.py index 6921ac8f877..a25f94496e6 100644 --- a/lib/ansible/plugins/loader.py +++ b/lib/ansible/plugins/loader.py @@ -407,8 +407,8 @@ class PluginLoader: ret = [] for i in paths: if i not in ret: - ret.append(to_text(i, errors='surrogate_or_strict')) - return os.pathsep.join(ret) + ret.append(i) + return to_text(os.pathsep.join(ret), errors='surrogate_or_strict') def print_paths(self): return self.format_paths(self._get_paths(subdirs=False))