diff --git a/changelogs/fragments/82708-unsafe-plugin-name-error.yml b/changelogs/fragments/82708-unsafe-plugin-name-error.yml new file mode 100644 index 00000000000..1de42c31adc --- /dev/null +++ b/changelogs/fragments/82708-unsafe-plugin-name-error.yml @@ -0,0 +1,2 @@ +bugfixes: + - "Fix an issue when setting a plugin name from an unsafe source resulted in ``ValueError: unmarshallable object`` (https://github.com/ansible/ansible/issues/82708)" diff --git a/lib/ansible/plugins/loader.py b/lib/ansible/plugins/loader.py index 8b7fbfce823..2e8a77e398b 100644 --- a/lib/ansible/plugins/loader.py +++ b/lib/ansible/plugins/loader.py @@ -34,6 +34,7 @@ from ansible.utils.collection_loader import AnsibleCollectionConfig, AnsibleColl from ansible.utils.collection_loader._collection_finder import _AnsibleCollectionFinder, _get_collection_metadata from ansible.utils.display import Display from ansible.utils.plugin_docs import add_fragments, find_plugin_docfile +from ansible.utils.unsafe_proxy import _is_unsafe # TODO: take the packaging dep, or vendor SpecifierSet? @@ -858,6 +859,17 @@ class PluginLoader: def get_with_context(self, name, *args, **kwargs): ''' instantiates a plugin of the given name using arguments ''' + if _is_unsafe(name): + # Objects constructed using the name wrapped as unsafe remain + # (correctly) unsafe. Using such unsafe objects in places + # where underlying types (builtin string in this case) are + # expected can cause problems. + # One such case is importlib.abc.Loader.exec_module failing + # with "ValueError: unmarshallable object" because the module + # object is created with the __path__ attribute being wrapped + # as unsafe which isn't marshallable. + # Manually removing the unsafe wrapper prevents such issues. + name = name._strip_unsafe() found_in_cache = True class_only = kwargs.pop('class_only', False) diff --git a/test/integration/targets/plugin_loader/collections/ansible_collections/n/c/plugins/action/a.py b/test/integration/targets/plugin_loader/collections/ansible_collections/n/c/plugins/action/a.py new file mode 100644 index 00000000000..685b1597d27 --- /dev/null +++ b/test/integration/targets/plugin_loader/collections/ansible_collections/n/c/plugins/action/a.py @@ -0,0 +1,6 @@ +from ansible.plugins.action import ActionBase + + +class ActionModule(ActionBase): + def run(self, tmp=None, task_vars=None): + return {"nca_executed": True} diff --git a/test/integration/targets/plugin_loader/runme.sh b/test/integration/targets/plugin_loader/runme.sh index e30f62419b3..7c3169f27a0 100755 --- a/test/integration/targets/plugin_loader/runme.sh +++ b/test/integration/targets/plugin_loader/runme.sh @@ -34,3 +34,5 @@ done # test config loading ansible-playbook use_coll_name.yml -i ../../inventory -e 'ansible_connection=ansible.builtin.ssh' "$@" + +ANSIBLE_COLLECTIONS_PATH=$PWD/collections ansible-playbook unsafe_plugin_name.yml "$@" diff --git a/test/integration/targets/plugin_loader/unsafe_plugin_name.yml b/test/integration/targets/plugin_loader/unsafe_plugin_name.yml new file mode 100644 index 00000000000..73cd4399ca9 --- /dev/null +++ b/test/integration/targets/plugin_loader/unsafe_plugin_name.yml @@ -0,0 +1,9 @@ +- hosts: localhost + gather_facts: false + tasks: + - action: !unsafe n.c.a + register: r + + - assert: + that: + - r.nca_executed