diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
commit | a453ac31f3428614cceb99027f8efbdb9258a40b (patch) | |
tree | f61f87408f32a8511cbd91799f9cececb53e0374 /test/integration/targets/collections | |
parent | Initial commit. (diff) | |
download | ansible-a453ac31f3428614cceb99027f8efbdb9258a40b.tar.xz ansible-a453ac31f3428614cceb99027f8efbdb9258a40b.zip |
Adding upstream version 2.10.7+merged+base+2.10.8+dfsg.upstream/2.10.7+merged+base+2.10.8+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test/integration/targets/collections')
119 files changed, 2675 insertions, 0 deletions
diff --git a/test/integration/targets/collections/a.statichost.yml b/test/integration/targets/collections/a.statichost.yml new file mode 100644 index 00000000..683878aa --- /dev/null +++ b/test/integration/targets/collections/a.statichost.yml @@ -0,0 +1,3 @@ +# use a plugin defined in a content-adjacent collection to ensure we added it properly +plugin: testns.content_adj.statichost +hostname: dynamic_host_a diff --git a/test/integration/targets/collections/aliases b/test/integration/targets/collections/aliases new file mode 100644 index 00000000..1a9cc499 --- /dev/null +++ b/test/integration/targets/collections/aliases @@ -0,0 +1,4 @@ +posix +shippable/posix/group4 +shippable/windows/group1 +windows diff --git a/test/integration/targets/collections/cache.statichost.yml b/test/integration/targets/collections/cache.statichost.yml new file mode 100644 index 00000000..b2adcfa6 --- /dev/null +++ b/test/integration/targets/collections/cache.statichost.yml @@ -0,0 +1,7 @@ +# use inventory and cache plugins defined in a content-adjacent collection +plugin: testns.content_adj.statichost +hostname: cache_host_a +cache_plugin: testns.content_adj.custom_jsonfile +cache: yes +cache_connection: inventory_cache +cache_prefix: 'prefix_' diff --git a/test/integration/targets/collections/check_populated_inventory.yml b/test/integration/targets/collections/check_populated_inventory.yml new file mode 100644 index 00000000..ab33081a --- /dev/null +++ b/test/integration/targets/collections/check_populated_inventory.yml @@ -0,0 +1,11 @@ +--- +- hosts: localhost + connection: local + gather_facts: no + tasks: + - assert: + that: + - "groups.all | length == 2" + - "groups.ungrouped == groups.all" + - "'cache_host_a' in groups.all" + - "'dynamic_host_a' in groups.all" diff --git a/test/integration/targets/collections/collection_root_sys/ansible_collections/testns/coll_in_sys/plugins/modules/systestmodule.py b/test/integration/targets/collections/collection_root_sys/ansible_collections/testns/coll_in_sys/plugins/modules/systestmodule.py new file mode 100644 index 00000000..cba38120 --- /dev/null +++ b/test/integration/targets/collections/collection_root_sys/ansible_collections/testns/coll_in_sys/plugins/modules/systestmodule.py @@ -0,0 +1,13 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + + +def main(): + print(json.dumps(dict(changed=False, source='sys'))) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_sys/ansible_collections/testns/testcoll/plugins/modules/maskedmodule.py b/test/integration/targets/collections/collection_root_sys/ansible_collections/testns/testcoll/plugins/modules/maskedmodule.py new file mode 100644 index 00000000..e3db81be --- /dev/null +++ b/test/integration/targets/collections/collection_root_sys/ansible_collections/testns/testcoll/plugins/modules/maskedmodule.py @@ -0,0 +1,13 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + + +def main(): + print(json.dumps(dict(changed=False, failed=True, msg='this collection should be masked by testcoll in the user content root'))) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_sys/ansible_collections/testns/testcoll/plugins/modules/testmodule.py b/test/integration/targets/collections/collection_root_sys/ansible_collections/testns/testcoll/plugins/modules/testmodule.py new file mode 100644 index 00000000..cba38120 --- /dev/null +++ b/test/integration/targets/collections/collection_root_sys/ansible_collections/testns/testcoll/plugins/modules/testmodule.py @@ -0,0 +1,13 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + + +def main(): + print(json.dumps(dict(changed=False, source='sys'))) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_sys/ansible_collections/testns/testcoll/roles/maskedrole/tasks/main.yml b/test/integration/targets/collections/collection_root_sys/ansible_collections/testns/testcoll/roles/maskedrole/tasks/main.yml new file mode 100644 index 00000000..21fe324a --- /dev/null +++ b/test/integration/targets/collections/collection_root_sys/ansible_collections/testns/testcoll/roles/maskedrole/tasks/main.yml @@ -0,0 +1,2 @@ +- fail: + msg: this role should never be visible or runnable diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/ansible/builtin/plugins/modules/ping.py b/test/integration/targets/collections/collection_root_user/ansible_collections/ansible/builtin/plugins/modules/ping.py new file mode 100644 index 00000000..07476709 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/ansible/builtin/plugins/modules/ping.py @@ -0,0 +1,13 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + + +def main(): + print(json.dumps(dict(changed=False, source='overridden ansible.builtin (should not be possible)'))) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/ansible/bullcoll/plugins/modules/bullmodule.py b/test/integration/targets/collections/collection_root_user/ansible_collections/ansible/bullcoll/plugins/modules/bullmodule.py new file mode 100644 index 00000000..5ea354e7 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/ansible/bullcoll/plugins/modules/bullmodule.py @@ -0,0 +1,13 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + + +def main(): + print(json.dumps(dict(changed=False, source='user_ansible_bullcoll'))) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/othercoll/plugins/module_utils/formerly_testcoll_pkg/__init__.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/othercoll/plugins/module_utils/formerly_testcoll_pkg/__init__.py new file mode 100644 index 00000000..aa5c3eed --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/othercoll/plugins/module_utils/formerly_testcoll_pkg/__init__.py @@ -0,0 +1 @@ +thing = "hello from testns.othercoll.formerly_testcoll_pkg.thing" diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/othercoll/plugins/module_utils/formerly_testcoll_pkg/submod.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/othercoll/plugins/module_utils/formerly_testcoll_pkg/submod.py new file mode 100644 index 00000000..eb49a163 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/othercoll/plugins/module_utils/formerly_testcoll_pkg/submod.py @@ -0,0 +1 @@ +thing = "hello from formerly_testcoll_pkg.submod.thing" diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testbroken/plugins/filter/broken_filter.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testbroken/plugins/filter/broken_filter.py new file mode 100644 index 00000000..51fe8524 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testbroken/plugins/filter/broken_filter.py @@ -0,0 +1,13 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +class FilterModule(object): + + def filters(self): + return { + 'broken': lambda x: 'broken', + } + + +raise Exception('This is a broken filter plugin.') diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/meta/runtime.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/meta/runtime.yml new file mode 100644 index 00000000..f5b617d9 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/meta/runtime.yml @@ -0,0 +1,52 @@ +plugin_routing: + action: + uses_redirected_action: + redirect: testns.testcoll.subclassed_normal + callback: + removedcallback: + tombstone: + removal_date: '2020-01-01' + connection: + redirected_local: + redirect: ansible.builtin.local + modules: + multilevel1: + redirect: testns.testcoll.multilevel2 + multilevel2: + redirect: testns.testcoll.multilevel3 + multilevel3: + redirect: testns.testcoll.ping + uses_redirected_action: + redirect: ansible.builtin.ping + setup.ps1: ansible.windows.setup + looped_ping: + redirect: testns.testcoll.looped_ping2 + looped_ping2: + redirect: testns.testcoll.looped_ping + bogus_redirect: + redirect: bogus.collection.shouldbomb + deprecated_ping: + deprecation: + removal_date: 2020-12-31 + warning_text: old_ping will be removed in a future release of this collection. Use new_ping instead. + foobar_facts: + redirect: foobar_info + aliased_ping: + redirect: ansible.builtin.ping + dead_ping: + tombstone: + removal_date: 2019-12-31 + warning_text: dead_ping has been removed + module_utils: + moved_out_root: + redirect: testns.content_adj.sub1.foomodule + formerly_testcoll_pkg: + redirect: ansible_collections.testns.othercoll.plugins.module_utils.formerly_testcoll_pkg + formerly_testcoll_pkg.submod: + redirect: ansible_collections.testns.othercoll.plugins.module_utils.formerly_testcoll_pkg.submod + missing_redirect_target_collection: + redirect: bogusns.boguscoll.bogusmu + missing_redirect_target_module: + redirect: testns.othercoll.bogusmu + +requires_ansible: '>=2.11' diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/default_collection_playbook.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/default_collection_playbook.yml new file mode 100644 index 00000000..1d1aee7d --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/default_collection_playbook.yml @@ -0,0 +1,49 @@ +# verify default collection action/module lookup works +# since we're running this playbook inside a collection, it will set that collection as the default search for all playboooks +# and non-collection roles to allow for easy migration of old integration tests to collections +- hosts: testhost + tasks: + - testmodule: + +- hosts: testhost + vars: + test_role_input: task static default collection + tasks: + - import_role: + name: testrole # unqualified role lookup should work; inheriting from the containing collection + - assert: + that: + - test_role_output.msg == test_role_input + - vars: + test_role_input: task static legacy embedded default collection + block: + - import_role: + name: non_coll_role + - assert: + that: + - test_role_output.msg == test_role_input + +- hosts: testhost + vars: + test_role_input: keyword static default collection + roles: + - testrole + tasks: + - debug: var=test_role_input + - debug: var=test_role_output + - assert: + that: + - test_role_output.msg == test_role_input + +- hosts: testhost + vars: + test_role_input: task dynamic default collection + tasks: + - include_role: + name: testrole # unqualified role lookup should work; inheriting from the containing collection + - include_role: + name: non_coll_role + - assert: + that: + - testmodule_out_from_non_coll_role is success + - embedded_module_out_from_non_coll_role is success diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role/library/embedded_module.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role/library/embedded_module.py new file mode 100644 index 00000000..54402d12 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role/library/embedded_module.py @@ -0,0 +1,13 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + + +def main(): + print(json.dumps(dict(changed=False, source='collection_embedded_non_collection_role'))) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role/tasks/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role/tasks/main.yml new file mode 100644 index 00000000..d41ae90e --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role/tasks/main.yml @@ -0,0 +1,29 @@ +- testmodule: + register: testmodule_out_from_non_coll_role + +- embedded_module: + register: embedded_module_out_from_non_coll_role + +- name: check collections list from role meta + plugin_lookup: + register: pluginlookup_out + +- debug: var=pluginlookup_out + +- debug: + msg: '{{ test_role_input | default("(undefined)") }}' + register: test_role_output + +- assert: + that: + - test_role_input is not defined or test_role_input == test_role_output.msg + +- vars: + test_role_input: include another non-coll role + block: + - include_role: + name: non_coll_role_to_call + + - assert: + that: + - test_role_output.msg == test_role_input diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role_to_call/tasks/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role_to_call/tasks/main.yml new file mode 100644 index 00000000..98445ce3 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role_to_call/tasks/main.yml @@ -0,0 +1,7 @@ +- debug: + msg: '{{ test_role_input | default("(undefined)") }}' + register: test_role_output + +- assert: + that: + - test_role_input is not defined or test_role_input == test_role_output.msg diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/action_subdir/subdir_ping_action.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/action_subdir/subdir_ping_action.py new file mode 100644 index 00000000..5af73342 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/action_subdir/subdir_ping_action.py @@ -0,0 +1,19 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.plugins.action import ActionBase + + +class ActionModule(ActionBase): + TRANSFERS_FILES = False + _VALID_ARGS = frozenset() + + def run(self, tmp=None, task_vars=None): + if task_vars is None: + task_vars = dict() + + result = super(ActionModule, self).run(None, task_vars) + + result = dict(changed=False) + + return result diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/bypass_host_loop.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/bypass_host_loop.py new file mode 100644 index 00000000..b15493d9 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/bypass_host_loop.py @@ -0,0 +1,17 @@ +# Copyright: (c) 2020, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.plugins.action import ActionBase + + +class ActionModule(ActionBase): + + BYPASS_HOST_LOOP = True + + def run(self, tmp=None, task_vars=None): + result = super(ActionModule, self).run(tmp, task_vars) + result['bypass_inventory_hostname'] = task_vars['inventory_hostname'] + return result diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/plugin_lookup.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/plugin_lookup.py new file mode 100644 index 00000000..3fa41e8f --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/plugin_lookup.py @@ -0,0 +1,40 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.plugins.action import ActionBase +from ansible.plugins import loader + + +class ActionModule(ActionBase): + TRANSFERS_FILES = False + _VALID_ARGS = frozenset(('type', 'name')) + + def run(self, tmp=None, task_vars=None): + if task_vars is None: + task_vars = dict() + + result = super(ActionModule, self).run(None, task_vars) + + plugin_type = self._task.args.get('type') + name = self._task.args.get('name') + + result = dict(changed=False, collection_list=self._task.collections) + + if all([plugin_type, name]): + attr_name = '{0}_loader'.format(plugin_type) + + typed_loader = getattr(loader, attr_name, None) + + if not typed_loader: + return (dict(failed=True, msg='invalid plugin type {0}'.format(plugin_type))) + + context = typed_loader.find_plugin_with_context(name, collection_list=self._task.collections) + + if not context.resolved: + result['plugin_path'] = None + result['redirect_list'] = [] + else: + result['plugin_path'] = context.plugin_resolved_path + result['redirect_list'] = context.redirect_list + + return result diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/subclassed_normal.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/subclassed_normal.py new file mode 100644 index 00000000..f0eff30b --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/subclassed_normal.py @@ -0,0 +1,11 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.plugins.action.normal import ActionModule as NormalAction + + +class ActionModule(NormalAction): + def run(self, *args, **kwargs): + result = super(ActionModule, self).run(*args, **kwargs) + result['hacked'] = 'I got run under a subclassed normal, yay' + return result diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/uses_redirected_import.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/uses_redirected_import.py new file mode 100644 index 00000000..701d7b46 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/action/uses_redirected_import.py @@ -0,0 +1,20 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.plugins.action import ActionBase +from ansible.module_utils.formerly_core import thingtocall + + +class ActionModule(ActionBase): + TRANSFERS_FILES = False + _VALID_ARGS = frozenset() + + def run(self, tmp=None, task_vars=None): + if task_vars is None: + task_vars = dict() + + result = super(ActionModule, self).run(None, task_vars) + + result = dict(changed=False, ttc_res=thingtocall()) + + return result diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/callback/usercallback.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/callback/usercallback.py new file mode 100644 index 00000000..c5b0f66a --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/callback/usercallback.py @@ -0,0 +1,27 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.plugins.callback import CallbackBase + +DOCUMENTATION = ''' + callback: usercallback + callback_type: notification + short_description: does stuff + description: + - does some stuff +''' + + +class CallbackModule(CallbackBase): + CALLBACK_VERSION = 2.0 + CALLBACK_TYPE = 'aggregate' + CALLBACK_NAME = 'usercallback' + CALLBACK_NEEDS_WHITELIST = True + + def __init__(self): + + super(CallbackModule, self).__init__() + self._display.display("loaded usercallback from collection, yay") + + def v2_runner_on_ok(self, result): + self._display.display("usercallback says ok") diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/connection/localconn.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/connection/localconn.py new file mode 100644 index 00000000..fc19a99d --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/connection/localconn.py @@ -0,0 +1,41 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils._text import to_native +from ansible.plugins.connection import ConnectionBase + +DOCUMENTATION = """ + connection: localconn + short_description: do stuff local + description: + - does stuff + options: + connectionvar: + description: + - something we set + default: the_default + vars: + - name: ansible_localconn_connectionvar +""" + + +class Connection(ConnectionBase): + transport = 'local' + has_pipelining = True + + def _connect(self): + return self + + def exec_command(self, cmd, in_data=None, sudoable=True): + stdout = 'localconn ran {0}'.format(to_native(cmd)) + stderr = 'connectionvar is {0}'.format(to_native(self.get_option('connectionvar'))) + return (0, stdout, stderr) + + def put_file(self, in_path, out_path): + raise NotImplementedError('just a test') + + def fetch_file(self, in_path, out_path): + raise NotImplementedError('just a test') + + def close(self): + self._connected = False diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/doc_fragments/frag.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/doc_fragments/frag.py new file mode 100644 index 00000000..4549f2d6 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/doc_fragments/frag.py @@ -0,0 +1,18 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +class ModuleDocFragment(object): + DOCUMENTATION = r''' +options: + normal_doc_frag: + description: + - an option +''' + + OTHER_DOCUMENTATION = r''' +options: + other_doc_frag: + description: + - another option +''' diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/filter/filter_subdir/my_subdir_filters.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/filter/filter_subdir/my_subdir_filters.py new file mode 100644 index 00000000..a5498a43 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/filter/filter_subdir/my_subdir_filters.py @@ -0,0 +1,14 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def test_subdir_filter(data): + return "{0}_via_testfilter_from_subdir".format(data) + + +class FilterModule(object): + + def filters(self): + return { + 'test_subdir_filter': test_subdir_filter + } diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/filter/myfilters.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/filter/myfilters.py new file mode 100644 index 00000000..0ce239e2 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/filter/myfilters.py @@ -0,0 +1,14 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def testfilter(data): + return "{0}_via_testfilter_from_userdir".format(data) + + +class FilterModule(object): + + def filters(self): + return { + 'testfilter': testfilter + } diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/filter/myfilters2.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/filter/myfilters2.py new file mode 100644 index 00000000..07239222 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/filter/myfilters2.py @@ -0,0 +1,14 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def testfilter2(data): + return "{0}_via_testfilter2_from_userdir".format(data) + + +class FilterModule(object): + + def filters(self): + return { + 'testfilter2': testfilter2 + } diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/lookup/lookup_subdir/my_subdir_lookup.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/lookup/lookup_subdir/my_subdir_lookup.py new file mode 100644 index 00000000..dd9818c9 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/lookup/lookup_subdir/my_subdir_lookup.py @@ -0,0 +1,11 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.plugins.lookup import LookupBase + + +class LookupModule(LookupBase): + + def run(self, terms, variables, **kwargs): + + return ['subdir_lookup_from_user_dir'] diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/lookup/mylookup.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/lookup/mylookup.py new file mode 100644 index 00000000..1cf3d28f --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/lookup/mylookup.py @@ -0,0 +1,11 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.plugins.lookup import LookupBase + + +class LookupModule(LookupBase): + + def run(self, terms, variables, **kwargs): + + return ['mylookup_from_user_dir'] diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/lookup/mylookup2.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/lookup/mylookup2.py new file mode 100644 index 00000000..bda671f5 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/lookup/mylookup2.py @@ -0,0 +1,12 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +from ansible.plugins.lookup import LookupBase + + +class LookupModule(LookupBase): + + def run(self, terms, variables, **kwargs): + + return ['mylookup2_from_user_dir'] diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/AnotherCSMU.cs b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/AnotherCSMU.cs new file mode 100644 index 00000000..68d2bc7a --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/AnotherCSMU.cs @@ -0,0 +1,12 @@ +using System; + +namespace ansible_collections.testns.testcoll.plugins.module_utils.AnotherCSMU +{ + public class AnotherThing + { + public static string CallMe() + { + return "Hello from nested user-collection-hosted AnotherCSMU"; + } + } +} diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/MyCSMU.cs b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/MyCSMU.cs new file mode 100644 index 00000000..2b7843d7 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/MyCSMU.cs @@ -0,0 +1,19 @@ +using System; + +using ansible_collections.testns.testcoll.plugins.module_utils.AnotherCSMU; +using ansible_collections.testns.testcoll.plugins.module_utils.subpkg.subcs; + +//TypeAccelerator -Name MyCSMU -TypeName CustomThing + +namespace ansible_collections.testns.testcoll.plugins.module_utils.MyCSMU +{ + public class CustomThing + { + public static string HelloWorld() + { + string res1 = AnotherThing.CallMe(); + string res2 = NestedUtil.HelloWorld(); + return String.Format("Hello from user_mu collection-hosted MyCSMU, also {0} and {1}", res1, res2); + } + } +} diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/MyPSMU.psm1 b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/MyPSMU.psm1 new file mode 100644 index 00000000..09da66d5 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/MyPSMU.psm1 @@ -0,0 +1,9 @@ +Function Invoke-FromUserPSMU { + <# + .SYNOPSIS + Test function + #> + return "from user_mu" +} + +Export-ModuleMember -Function Invoke-FromUserPSMU diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/base.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/base.py new file mode 100644 index 00000000..0654d182 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/base.py @@ -0,0 +1,12 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.testns.testcoll.plugins.module_utils import secondary +import ansible_collections.testns.testcoll.plugins.module_utils.secondary + + +def thingtocall(): + if secondary != ansible_collections.testns.testcoll.plugins.module_utils.secondary: + raise Exception() + + return "thingtocall in base called " + ansible_collections.testns.testcoll.plugins.module_utils.secondary.thingtocall() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/leaf.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/leaf.py new file mode 100644 index 00000000..ad847105 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/leaf.py @@ -0,0 +1,6 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def thingtocall(): + return "thingtocall in leaf" diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/nested_same/__init__.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/nested_same/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/nested_same/__init__.py diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/nested_same/nested_same/__init__.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/nested_same/nested_same/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/nested_same/nested_same/__init__.py diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/nested_same/nested_same/nested_same.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/nested_same/nested_same/nested_same.py new file mode 100644 index 00000000..77407564 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/nested_same/nested_same/nested_same.py @@ -0,0 +1,6 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def nested_same(): + return 'hello from nested_same' diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/secondary.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/secondary.py new file mode 100644 index 00000000..9a315686 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/secondary.py @@ -0,0 +1,6 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def thingtocall(): + return "thingtocall in secondary" diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg/__init__.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg/__init__.py diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg/subcs.cs b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg/subcs.cs new file mode 100644 index 00000000..ebeb8ce5 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg/subcs.cs @@ -0,0 +1,13 @@ +using System; + +namespace ansible_collections.testns.testcoll.plugins.module_utils.subpkg.subcs +{ + public class NestedUtil + { + public static string HelloWorld() + { + string res = "Hello from subpkg.subcs"; + return res; + } + } +} diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg/submod.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg/submod.py new file mode 100644 index 00000000..3c24bc44 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg/submod.py @@ -0,0 +1,6 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def thingtocall(): + return "thingtocall in subpkg.submod" diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg/subps.psm1 b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg/subps.psm1 new file mode 100644 index 00000000..1db0ab97 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg/subps.psm1 @@ -0,0 +1,9 @@ +Function Invoke-SubUserPSMU { + <# + .SYNOPSIS + Test function + #> + return "from subpkg.subps.psm1" +} + +Export-ModuleMember -Function Invoke-SubUserPSMU diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg_with_init.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg_with_init.py new file mode 100644 index 00000000..b48a717c --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg_with_init.py @@ -0,0 +1,11 @@ +# NB: this module should never be loaded, since we'll see the subpkg_with_init package dir first +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def thingtocall(): + raise Exception('this should never be called (loaded discrete module instead of package module)') + + +def anotherthingtocall(): + raise Exception('this should never be called (loaded discrete module instead of package module)') diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg_with_init/__init__.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg_with_init/__init__.py new file mode 100644 index 00000000..d424796f --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg_with_init/__init__.py @@ -0,0 +1,10 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +# exercise relative imports in package init; they behave differently +from .mod_in_subpkg_with_init import thingtocall as submod_thingtocall +from ..subpkg.submod import thingtocall as cousin_submod_thingtocall # pylint: disable=relative-beyond-top-level + + +def thingtocall(): + return "thingtocall in subpkg_with_init" diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg_with_init/mod_in_subpkg_with_init.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg_with_init/mod_in_subpkg_with_init.py new file mode 100644 index 00000000..27747dae --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/subpkg_with_init/mod_in_subpkg_with_init.py @@ -0,0 +1,6 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def thingtocall(): + return "thingtocall in mod_in_subpkg_with_init" diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/deprecated_ping.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/deprecated_ping.py new file mode 100644 index 00000000..9698ba6f --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/deprecated_ping.py @@ -0,0 +1,13 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + + +def main(): + print(json.dumps(dict(changed=False, source='user', is_deprecated=True))) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/module_subdir/subdir_ping_module.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/module_subdir/subdir_ping_module.py new file mode 100644 index 00000000..5a70174d --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/module_subdir/subdir_ping_module.py @@ -0,0 +1,14 @@ +#!/usr/bin/python + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + + +def main(): + print(json.dumps(dict(changed=False, source='user'))) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/ping.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/ping.py new file mode 100644 index 00000000..2ca079c6 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/ping.py @@ -0,0 +1,13 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + + +def main(): + print(json.dumps(dict(changed=False, source='user'))) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/testmodule.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/testmodule.py new file mode 100644 index 00000000..e2efadae --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/testmodule.py @@ -0,0 +1,21 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +DOCUMENTATION = r''' +module: testmodule +description: for testing +extends_documentation_fragment: + - testns.testcoll.frag + - testns.testcoll.frag.other_documentation +''' + + +def main(): + print(json.dumps(dict(changed=False, source='user'))) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/testmodule_bad_docfrags.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/testmodule_bad_docfrags.py new file mode 100644 index 00000000..46ccb76c --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/testmodule_bad_docfrags.py @@ -0,0 +1,25 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +DOCUMENTATION = r''' +module: testmodule +description: for testing +extends_documentation_fragment: + - noncollbogusfrag + - noncollbogusfrag.bogusvar + - bogusns.testcoll.frag + - testns.boguscoll.frag + - testns.testcoll.bogusfrag + - testns.testcoll.frag.bogusvar +''' + + +def main(): + print(json.dumps(dict(changed=False, source='user'))) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_base_mu_granular_nested_import.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_base_mu_granular_nested_import.py new file mode 100644 index 00000000..4054e36f --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_base_mu_granular_nested_import.py @@ -0,0 +1,19 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import sys + +from ansible_collections.testns.testcoll.plugins.module_utils.base import thingtocall + + +def main(): + mu_result = thingtocall() + print(json.dumps(dict(changed=False, source='user', mu_result=mu_result))) + + sys.exit() + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_collection_redirected_mu.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_collection_redirected_mu.py new file mode 100644 index 00000000..b169fdea --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_collection_redirected_mu.py @@ -0,0 +1,21 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import sys + +from ansible_collections.testns.testcoll.plugins.module_utils.moved_out_root import importme +from ..module_utils.formerly_testcoll_pkg import thing as movedthing # pylint: disable=relative-beyond-top-level +from ..module_utils.formerly_testcoll_pkg.submod import thing as submodmovedthing # pylint: disable=relative-beyond-top-level + + +def main(): + mu_result = importme() + print(json.dumps(dict(changed=False, source='user', mu_result=mu_result, mu_result2=movedthing, mu_result3=submodmovedthing))) + + sys.exit() + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_core_redirected_mu.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_core_redirected_mu.py new file mode 100644 index 00000000..28a07729 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_core_redirected_mu.py @@ -0,0 +1,19 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import sys + +from ansible.module_utils.formerly_core import thingtocall + + +def main(): + mu_result = thingtocall() + print(json.dumps(dict(changed=False, source='user', mu_result=mu_result))) + + sys.exit() + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_leaf_mu_flat_import.bak b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_leaf_mu_flat_import.bak new file mode 100644 index 00000000..703f4548 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_leaf_mu_flat_import.bak @@ -0,0 +1,3 @@ +# Intentionally blank, and intentionally attempting to shadow +# uses_leaf_mu_flat_import.py. MODULE_IGNORE_EXTS should prevent this file +# from ever being loaded. diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_leaf_mu_flat_import.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_leaf_mu_flat_import.py new file mode 100644 index 00000000..295d4329 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_leaf_mu_flat_import.py @@ -0,0 +1,19 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import sys + +import ansible_collections.testns.testcoll.plugins.module_utils.leaf + + +def main(): + mu_result = ansible_collections.testns.testcoll.plugins.module_utils.leaf.thingtocall() + print(json.dumps(dict(changed=False, source='user', mu_result=mu_result))) + + sys.exit() + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_leaf_mu_flat_import.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_leaf_mu_flat_import.yml new file mode 100644 index 00000000..703f4548 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_leaf_mu_flat_import.yml @@ -0,0 +1,3 @@ +# Intentionally blank, and intentionally attempting to shadow +# uses_leaf_mu_flat_import.py. MODULE_IGNORE_EXTS should prevent this file +# from ever being loaded. diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_leaf_mu_granular_import.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_leaf_mu_granular_import.py new file mode 100644 index 00000000..3794f496 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_leaf_mu_granular_import.py @@ -0,0 +1,19 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import sys + +from ansible_collections.testns.testcoll.plugins.module_utils.leaf import thingtocall as aliasedthing + + +def main(): + mu_result = aliasedthing() + print(json.dumps(dict(changed=False, source='user', mu_result=mu_result))) + + sys.exit() + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_leaf_mu_module_import_from.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_leaf_mu_module_import_from.py new file mode 100644 index 00000000..559e3e56 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_leaf_mu_module_import_from.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import sys + +from ansible_collections.testns.testcoll.plugins.module_utils import leaf, secondary +# FIXME: this one needs pkginit synthesis to work +# from ansible_collections.testns.testcoll.plugins.module_utils.subpkg import submod +from ansible_collections.testns.testcoll.plugins.module_utils.subpkg_with_init import (thingtocall as spwi_thingtocall, + submod_thingtocall as spwi_submod_thingtocall, + cousin_submod_thingtocall as spwi_cousin_submod_thingtocall) + + +def main(): + mu_result = leaf.thingtocall() + mu2_result = secondary.thingtocall() + mu3_result = "thingtocall in subpkg.submod" # FIXME: this one needs pkginit synthesis to work + # mu3_result = submod.thingtocall() + mu4_result = spwi_thingtocall() + mu5_result = spwi_submod_thingtocall() + mu6_result = spwi_cousin_submod_thingtocall() + print(json.dumps(dict(changed=False, source='user', mu_result=mu_result, mu2_result=mu2_result, + mu3_result=mu3_result, mu4_result=mu4_result, mu5_result=mu5_result, mu6_result=mu6_result))) + + sys.exit() + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing.py new file mode 100644 index 00000000..b945eb68 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing.py @@ -0,0 +1,16 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import sys + +from ..module_utils import bogusmu # pylint: disable=relative-beyond-top-level + + +def main(): + raise Exception('should never get here') + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_collection.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_collection.py new file mode 100644 index 00000000..59cb3c5e --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_collection.py @@ -0,0 +1,16 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import sys + +from ..module_utils import missing_redirect_target_collection # pylint: disable=relative-beyond-top-level + + +def main(): + raise Exception('should never get here') + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_module.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_module.py new file mode 100644 index 00000000..31ffd17c --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_module.py @@ -0,0 +1,16 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import sys + +from ..module_utils import missing_redirect_target_module # pylint: disable=relative-beyond-top-level + + +def main(): + raise Exception('should never get here') + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_nested_same_as_func.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_nested_same_as_func.py new file mode 100644 index 00000000..26fa53c0 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_nested_same_as_func.py @@ -0,0 +1,19 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import sys + +from ansible_collections.testns.testcoll.plugins.module_utils.nested_same.nested_same.nested_same import nested_same + + +def main(): + mu_result = nested_same() + print(json.dumps(dict(changed=False, source='user', mu_result=mu_result))) + + sys.exit() + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_nested_same_as_module.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_nested_same_as_module.py new file mode 100644 index 00000000..e017c14f --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_nested_same_as_module.py @@ -0,0 +1,19 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import sys + +from ansible_collections.testns.testcoll.plugins.module_utils.nested_same.nested_same import nested_same + + +def main(): + mu_result = nested_same.nested_same() + print(json.dumps(dict(changed=False, source='user', mu_result=mu_result))) + + sys.exit() + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_csbasic_only.ps1 b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_csbasic_only.ps1 new file mode 100644 index 00000000..df175831 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_csbasic_only.ps1 @@ -0,0 +1,22 @@ +#!powershell + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +#AnsibleRequires -CSharpUtil Ansible.Basic + +$spec = @{ + options = @{ + data = @{ type = "str"; default = "pong" } + } + supports_check_mode = $true +} +$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) +$data = $module.Params.data + +if ($data -eq "crash") { + throw "boom" +} + +$module.Result.ping = $data +$module.Result.source = "user" +$module.ExitJson()
\ No newline at end of file diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_selfcontained.ps1 b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_selfcontained.ps1 new file mode 100644 index 00000000..661bc0f6 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_selfcontained.ps1 @@ -0,0 +1,9 @@ +#!powershell + +$res = @{ + changed = $false + source = "user" + msg = "hi from selfcontained.ps1" +} + +ConvertTo-Json $res
\ No newline at end of file diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_selfcontained.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_selfcontained.py new file mode 100644 index 00000000..ce99bfa5 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_selfcontained.py @@ -0,0 +1 @@ +# docs for Windows module would go here; just ensure we don't accidentally load this instead of the .ps1 diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_uses_coll_csmu.ps1 b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_uses_coll_csmu.ps1 new file mode 100644 index 00000000..af00627b --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_uses_coll_csmu.ps1 @@ -0,0 +1,26 @@ +#!powershell + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +#AnsibleRequires -CSharpUtil Ansible.Basic +#AnsibleRequires -CSharpUtil ansible_collections.testns.testcoll.plugins.module_utils.MyCSMU +#AnsibleRequires -CSharpUtil ansible_collections.testns.testcoll.plugins.module_utils.subpkg.subcs + +$spec = @{ + options = @{ + data = @{ type = "str"; default = "called from $([ansible_collections.testns.testcoll.plugins.module_utils.MyCSMU.CustomThing]::HelloWorld())" } + } + supports_check_mode = $true +} +$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) +$data = $module.Params.data + +if ($data -eq "crash") { + throw "boom" +} + +$module.Result.ping = $data +$module.Result.source = "user" +$module.Result.subpkg = [ansible_collections.testns.testcoll.plugins.module_utils.subpkg.subcs.NestedUtil]::HelloWorld() +$module.Result.type_accelerator = "called from $([MyCSMU]::HelloWorld())" +$module.ExitJson() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_uses_coll_psmu.ps1 b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_uses_coll_psmu.ps1 new file mode 100644 index 00000000..cbca7b70 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_uses_coll_psmu.ps1 @@ -0,0 +1,25 @@ +#!powershell + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +#AnsibleRequires -CSharpUtil Ansible.Basic +#AnsibleRequires -Powershell ansible_collections.testns.testcoll.plugins.module_utils.MyPSMU +#AnsibleRequires -PowerShell ansible_collections.testns.testcoll.plugins.module_utils.subpkg.subps + +$spec = @{ + options = @{ + data = @{ type = "str"; default = "called from $(Invoke-FromUserPSMU)" } + } + supports_check_mode = $true +} +$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) +$data = $module.Params.data + +if ($data -eq "crash") { + throw "boom" +} + +$module.Result.ping = $data +$module.Result.source = "user" +$module.Result.subpkg = Invoke-SubUserPSMU +$module.ExitJson() diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/test/mytests.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/test/mytests.py new file mode 100644 index 00000000..ba610fb2 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/test/mytests.py @@ -0,0 +1,13 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def testtest(data): + return data == 'from_user' + + +class TestModule(object): + def tests(self): + return { + 'testtest': testtest + } diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/test/mytests2.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/test/mytests2.py new file mode 100644 index 00000000..183944ff --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/test/mytests2.py @@ -0,0 +1,13 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def testtest(data): + return data == 'from_user2' + + +class TestModule(object): + def tests(self): + return { + 'testtest2': testtest + } diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/test/test_subdir/my_subdir_tests.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/test/test_subdir/my_subdir_tests.py new file mode 100644 index 00000000..98a8f893 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/test/test_subdir/my_subdir_tests.py @@ -0,0 +1,13 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def subdir_test(data): + return data == 'subdir_from_user' + + +class TestModule(object): + def tests(self): + return { + 'subdir_test': subdir_test + } diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/vars/custom_vars.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/vars/custom_vars.py new file mode 100644 index 00000000..c603d72e --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/vars/custom_vars.py @@ -0,0 +1,44 @@ +# Copyright 2019 RedHat, inc +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +############################################# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' + vars: custom_vars + version_added: "2.10" + short_description: load host and group vars + description: test loading host and group vars from a collection + options: + stage: + choices: ['all', 'inventory', 'task'] + type: str + ini: + - key: stage + section: custom_vars + env: + - name: ANSIBLE_VARS_PLUGIN_STAGE +''' + +from ansible.plugins.vars import BaseVarsPlugin + + +class VarsModule(BaseVarsPlugin): + + def get_vars(self, loader, path, entities, cache=True): + super(VarsModule, self).get_vars(loader, path, entities) + return {'collection': 'collection_root_user'} diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/call_standalone/tasks/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/call_standalone/tasks/main.yml new file mode 100644 index 00000000..f5dcc0fc --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/call_standalone/tasks/main.yml @@ -0,0 +1,6 @@ +- include_role: + name: standalone + +- assert: + that: + - standalone_role_var is defined diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/calls_intra_collection_dep_role_unqualified/meta/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/calls_intra_collection_dep_role_unqualified/meta/main.yml new file mode 100644 index 00000000..b3a88198 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/calls_intra_collection_dep_role_unqualified/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - testrole # since testrole lives in this collection, we'll check there first diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/calls_intra_collection_dep_role_unqualified/tasks/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/calls_intra_collection_dep_role_unqualified/tasks/main.yml new file mode 100644 index 00000000..99297f70 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/calls_intra_collection_dep_role_unqualified/tasks/main.yml @@ -0,0 +1,7 @@ +- debug: + msg: '{{ outer_role_input | default("(undefined)") }}' + register: outer_role_output + +- assert: + that: + - outer_role_input is not defined or outer_role_input == outer_role_output.msg diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/common_handlers/handlers/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/common_handlers/handlers/main.yml new file mode 100644 index 00000000..186368f5 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/common_handlers/handlers/main.yml @@ -0,0 +1,27 @@ +# This handler should only be called 1 time, if it's called more than once +# this task should fail on subsequent executions +- name: test_fqcn_handler + set_fact: + handler_counter: '{{ handler_counter|int + 1 }}' + failed_when: handler_counter|int > 1 + +# The following handler contains the role name and should be callable as: +# 'common_handlers test_fqcn_handler' +# 'common_handlers : common_handlers test_fqcn_handler` +# 'testns.testcoll.common_handlers : common_handlers test_fqcn_handler' +- name: common_handlers test_fqcn_handler + set_fact: + handler_counter: '{{ handler_counter|int + 1}}' + failed_when: handler_counter|int > 2 + +# The following handler starts with 'role name : ' and should _not_ be listed as: +# 'common_handlers : common_handlers : test_fqcn_handler` +# 'testns.testcoll.common_handlers : common_handlers : test_fqcn_handler' +- name: 'common_handlers : test_fqcn_handler' + meta: noop + +# The following handler starts with 'fqcn : ' and should _not_ be listed as: +# 'common_handlers : testns.testcoll.common_handlers : test_fqcn_handler` +# 'testns.testcoll.common_handlers : testns.testcoll.common_handlers : test_fqcn_handler' +- name: 'testns.testcoll.common_handlers : test_fqcn_handler' + meta: noop diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/role_subdir/subdir_testrole/tasks/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/role_subdir/subdir_testrole/tasks/main.yml new file mode 100644 index 00000000..64f5242b --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/role_subdir/subdir_testrole/tasks/main.yml @@ -0,0 +1,10 @@ +- debug: + msg: '{{ test_role_input | default("(undefined)") }}' + register: test_role_output + +- set_fact: + testrole_source: collection + +- assert: + that: + - test_role_input is not defined or test_role_input == test_role_output.msg diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/test_fqcn_handlers/meta/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/test_fqcn_handlers/meta/main.yml new file mode 100644 index 00000000..9218f3d7 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/test_fqcn_handlers/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - testns.testcoll.common_handlers diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/test_fqcn_handlers/tasks/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/test_fqcn_handlers/tasks/main.yml new file mode 100644 index 00000000..6eadb7c2 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/test_fqcn_handlers/tasks/main.yml @@ -0,0 +1,16 @@ +- name: Fire fqcn handler 1 + debug: + msg: Fire fqcn handler + changed_when: true + notify: + - 'testns.testcoll.common_handlers : test_fqcn_handler' + - 'common_handlers : test_fqcn_handler' + - 'test_fqcn_handler' + +- debug: + msg: Fire fqcn handler with role name + changed_when: true + notify: + - 'testns.testcoll.common_handlers : common_handlers test_fqcn_handler' + - 'common_handlers : common_handlers test_fqcn_handler' + - 'common_handlers test_fqcn_handler' diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/testrole/meta/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/testrole/meta/main.yml new file mode 100644 index 00000000..8c22c1c6 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/testrole/meta/main.yml @@ -0,0 +1,4 @@ +collections: +- ansible.builtin +- testns.coll_in_sys +- bogus.fromrolemeta diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/testrole/tasks/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/testrole/tasks/main.yml new file mode 100644 index 00000000..7c05abb1 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/testrole/tasks/main.yml @@ -0,0 +1,39 @@ +# test using builtin module of multiple types in a role in a collection +# https://github.com/ansible/ansible/issues/65298 +- name: Run setup module because there is both setup.ps1 and setup.py + setup: + gather_subset: min + +- name: check collections list from role meta + plugin_lookup: + register: pluginlookup_out + +- name: call role-local ping module + ping: + register: ping_out + +- name: call unqualified module in another collection listed in role meta (testns.coll_in_sys) + systestmodule: + register: systestmodule_out + +# verify that pluginloader caching doesn't prevent us from explicitly calling a builtin plugin with the same name +- name: call builtin ping module explicitly + ansible.builtin.ping: + register: builtinping_out + +- debug: + msg: '{{ test_role_input | default("(undefined)") }}' + register: test_role_output + +- set_fact: + testrole_source: collection + +# FIXME: add tests to ensure that block/task level stuff in a collection-hosted role properly inherit role default/meta values + +- assert: + that: + - pluginlookup_out.collection_list == ['testns.testcoll', 'ansible.builtin', 'testns.coll_in_sys', 'bogus.fromrolemeta'] + - ping_out.source is defined and ping_out.source == 'user' + - systestmodule_out.source is defined and systestmodule_out.source == 'sys' + - builtinping_out.ping is defined and builtinping_out.ping == 'pong' + - test_role_input is not defined or test_role_input == test_role_output.msg diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/testrole_main_yaml/meta/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/testrole_main_yaml/meta/main.yml new file mode 100644 index 00000000..8c22c1c6 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/testrole_main_yaml/meta/main.yml @@ -0,0 +1,4 @@ +collections: +- ansible.builtin +- testns.coll_in_sys +- bogus.fromrolemeta diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/testrole_main_yaml/tasks/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/testrole_main_yaml/tasks/main.yml new file mode 100644 index 00000000..31e3af5e --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/roles/testrole_main_yaml/tasks/main.yml @@ -0,0 +1,33 @@ +- name: check collections list from role meta + plugin_lookup: + register: pluginlookup_out + +- name: call role-local ping module + ping: + register: ping_out + +- name: call unqualified module in another collection listed in role meta (testns.coll_in_sys) + systestmodule: + register: systestmodule_out + +# verify that pluginloader caching doesn't prevent us from explicitly calling a builtin plugin with the same name +- name: call builtin ping module explicitly + ansible.builtin.ping: + register: builtinping_out + +- debug: + msg: '{{ test_role_input | default("(undefined)") }}' + register: test_role_output + +- set_fact: + testrole_source: collection + +# FIXME: add tests to ensure that block/task level stuff in a collection-hosted role properly inherit role default/meta values + +- assert: + that: + - pluginlookup_out.collection_list == ['testns.testcoll', 'ansible.builtin', 'testns.coll_in_sys', 'bogus.fromrolemeta'] + - ping_out.source is defined and ping_out.source == 'user' + - systestmodule_out.source is defined and systestmodule_out.source == 'sys' + - builtinping_out.ping is defined and builtinping_out.ping == 'pong' + - test_role_input is not defined or test_role_input == test_role_output.msg diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testredirect/meta/runtime.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testredirect/meta/runtime.yml new file mode 100644 index 00000000..da8e4901 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testredirect/meta/runtime.yml @@ -0,0 +1,4 @@ +plugin_routing: + modules: + ping: + redirect: testns.testcoll.ping diff --git a/test/integration/targets/collections/collections/ansible_collections/me/mycoll1/plugins/action/action1.py b/test/integration/targets/collections/collections/ansible_collections/me/mycoll1/plugins/action/action1.py new file mode 100644 index 00000000..e9f97311 --- /dev/null +++ b/test/integration/targets/collections/collections/ansible_collections/me/mycoll1/plugins/action/action1.py @@ -0,0 +1,29 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.plugins.action import ActionBase + + +class ActionModule(ActionBase): + + def run(self, tmp=None, task_vars=None): + ''' handler for file transfer operations ''' + if task_vars is None: + task_vars = dict() + + result = super(ActionModule, self).run(tmp, task_vars) + + if result.get('skipped'): + return result + + module_args = self._task.args.copy() + + result.update( + self._execute_module( + module_name='me.mycoll2.module1', + module_args=module_args, + task_vars=task_vars, + ) + ) + + return result diff --git a/test/integration/targets/collections/collections/ansible_collections/me/mycoll1/plugins/modules/action1.py b/test/integration/targets/collections/collections/ansible_collections/me/mycoll1/plugins/modules/action1.py new file mode 100644 index 00000000..66bb5a41 --- /dev/null +++ b/test/integration/targets/collections/collections/ansible_collections/me/mycoll1/plugins/modules/action1.py @@ -0,0 +1,24 @@ +#!/usr/bin/python +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['stableinterface'], + 'supported_by': 'core'} + +DOCUMENTATION = ''' +--- +module: action1 +short_description: Action Test module +description: + - Action Test module +author: + - Ansible Core Team +''' + +EXAMPLES = ''' +''' + +RETURN = ''' +''' diff --git a/test/integration/targets/collections/collections/ansible_collections/me/mycoll2/plugins/modules/module1.py b/test/integration/targets/collections/collections/ansible_collections/me/mycoll2/plugins/modules/module1.py new file mode 100644 index 00000000..00bb993b --- /dev/null +++ b/test/integration/targets/collections/collections/ansible_collections/me/mycoll2/plugins/modules/module1.py @@ -0,0 +1,43 @@ +#!/usr/bin/python +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['stableinterface'], + 'supported_by': 'core'} + +DOCUMENTATION = ''' +--- +module: module1 +short_description: module1 Test module +description: + - module1 Test module +author: + - Ansible Core Team +''' + +EXAMPLES = ''' +''' + +RETURN = ''' +''' + + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + module = AnsibleModule( + argument_spec=dict( + desc=dict(type='str'), + ), + ) + + results = dict(msg="you just ran me.mycoll2.module1", desc=module.params.get('desc')) + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/cache/custom_jsonfile.py b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/cache/custom_jsonfile.py new file mode 100644 index 00000000..7605dc41 --- /dev/null +++ b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/cache/custom_jsonfile.py @@ -0,0 +1,63 @@ +# (c) 2014, Brian Coca, Josh Drake, et al +# (c) 2017 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 = ''' + cache: jsonfile + short_description: JSON formatted files. + description: + - This cache uses JSON formatted, per host, files saved to the filesystem. + version_added: "1.9" + author: Ansible Core (@ansible-core) + options: + _uri: + required: True + description: + - Path in which the cache plugin will save the JSON files + env: + - name: ANSIBLE_CACHE_PLUGIN_CONNECTION + ini: + - key: fact_caching_connection + section: defaults + _prefix: + description: User defined prefix to use when creating the JSON files + env: + - name: ANSIBLE_CACHE_PLUGIN_PREFIX + ini: + - key: fact_caching_prefix + section: defaults + _timeout: + default: 86400 + description: Expiration timeout for the cache plugin data + env: + - name: ANSIBLE_CACHE_PLUGIN_TIMEOUT + ini: + - key: fact_caching_timeout + section: defaults + type: integer +''' + +import codecs +import json + +from ansible.parsing.ajson import AnsibleJSONEncoder, AnsibleJSONDecoder +from ansible.plugins.cache import BaseFileCacheModule + + +class CacheModule(BaseFileCacheModule): + """ + A caching module backed by json files. + """ + + def _load(self, filepath): + # Valid JSON is always UTF-8 encoded. + with codecs.open(filepath, 'r', encoding='utf-8') as f: + return json.load(f, cls=AnsibleJSONDecoder) + + def _dump(self, value, filepath): + with codecs.open(filepath, 'w', encoding='utf-8') as f: + f.write(json.dumps(value, cls=AnsibleJSONEncoder, sort_keys=True, indent=4)) diff --git a/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/inventory/statichost.py b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/inventory/statichost.py new file mode 100644 index 00000000..ae6941f3 --- /dev/null +++ b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/inventory/statichost.py @@ -0,0 +1,68 @@ +# Copyright (c) 2018 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' + inventory: statichost + short_description: Add a single host + description: Add a single host + extends_documentation_fragment: + - inventory_cache + options: + plugin: + description: plugin name (must be statichost) + required: true + hostname: + description: Toggle display of stderr even when script was successful + required: True +''' + +from ansible.errors import AnsibleParserError +from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable + + +class InventoryModule(BaseInventoryPlugin, Cacheable): + + NAME = 'testns.content_adj.statichost' + + def __init__(self): + + super(InventoryModule, self).__init__() + + self._hosts = set() + + def verify_file(self, path): + ''' Verify if file is usable by this plugin, base does minimal accessibility check ''' + + if not path.endswith('.statichost.yml') and not path.endswith('.statichost.yaml'): + return False + return super(InventoryModule, self).verify_file(path) + + def parse(self, inventory, loader, path, cache=None): + + super(InventoryModule, self).parse(inventory, loader, path) + + # Initialize and validate options + self._read_config_data(path) + + # Exercise cache + cache_key = self.get_cache_key(path) + attempt_to_read_cache = self.get_option('cache') and cache + cache_needs_update = self.get_option('cache') and not cache + if attempt_to_read_cache: + try: + host_to_add = self._cache[cache_key] + except KeyError: + cache_needs_update = True + if not attempt_to_read_cache or cache_needs_update: + host_to_add = self.get_option('hostname') + + # this is where the magic happens + self.inventory.add_host(host_to_add, 'all') + self._cache[cache_key] = host_to_add + + # self.inventory.add_group()... + # self.inventory.add_child()... + # self.inventory.set_variable().. diff --git a/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/module_utils/__init__.py b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/module_utils/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/module_utils/__init__.py diff --git a/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/module_utils/sub1/__init__.py b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/module_utils/sub1/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/module_utils/sub1/__init__.py diff --git a/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/module_utils/sub1/foomodule.py b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/module_utils/sub1/foomodule.py new file mode 100644 index 00000000..eeffe01e --- /dev/null +++ b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/module_utils/sub1/foomodule.py @@ -0,0 +1,6 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def importme(): + return "hello from {0}".format(__name__) diff --git a/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/modules/contentadjmodule.py b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/modules/contentadjmodule.py new file mode 100644 index 00000000..0fa98eb0 --- /dev/null +++ b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/modules/contentadjmodule.py @@ -0,0 +1,13 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + + +def main(): + print(json.dumps(dict(changed=False, source='content_adj'))) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/vars/custom_adj_vars.py b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/vars/custom_adj_vars.py new file mode 100644 index 00000000..0cd9a1d5 --- /dev/null +++ b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/vars/custom_adj_vars.py @@ -0,0 +1,45 @@ +# Copyright 2019 RedHat, inc +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +############################################# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' + vars: custom_adj_vars + version_added: "2.10" + short_description: load host and group vars + description: test loading host and group vars from a collection + options: + stage: + default: all + choices: ['all', 'inventory', 'task'] + type: str + ini: + - key: stage + section: custom_adj_vars + env: + - name: ANSIBLE_VARS_PLUGIN_STAGE +''' + +from ansible.plugins.vars import BaseVarsPlugin + + +class VarsModule(BaseVarsPlugin): + + def get_vars(self, loader, path, entities, cache=True): + super(VarsModule, self).get_vars(loader, path, entities) + return {'collection': 'adjacent', 'adj_var': 'value'} diff --git a/test/integration/targets/collections/custom_vars_plugins/v1_vars_plugin.py b/test/integration/targets/collections/custom_vars_plugins/v1_vars_plugin.py new file mode 100644 index 00000000..b5792d88 --- /dev/null +++ b/test/integration/targets/collections/custom_vars_plugins/v1_vars_plugin.py @@ -0,0 +1,37 @@ +# Copyright 2019 RedHat, inc +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +############################################# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' + vars: v1_vars_plugin + version_added: "2.10" + short_description: load host and group vars + description: + - 3rd party vars plugin to test loading host and group vars without requiring whitelisting and without a plugin-specific stage option + options: +''' + +from ansible.plugins.vars import BaseVarsPlugin + + +class VarsModule(BaseVarsPlugin): + + def get_vars(self, loader, path, entities, cache=True): + super(VarsModule, self).get_vars(loader, path, entities) + return {'collection': False, 'name': 'v1_vars_plugin', 'v1_vars_plugin': True} diff --git a/test/integration/targets/collections/custom_vars_plugins/v2_vars_plugin.py b/test/integration/targets/collections/custom_vars_plugins/v2_vars_plugin.py new file mode 100644 index 00000000..fc140162 --- /dev/null +++ b/test/integration/targets/collections/custom_vars_plugins/v2_vars_plugin.py @@ -0,0 +1,45 @@ +# Copyright 2019 RedHat, inc +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +############################################# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' + vars: v2_vars_plugin + version_added: "2.10" + short_description: load host and group vars + description: + - 3rd party vars plugin to test loading host and group vars without requiring whitelisting and with a plugin-specific stage option + options: + stage: + choices: ['all', 'inventory', 'task'] + type: str + ini: + - key: stage + section: other_vars_plugin + env: + - name: ANSIBLE_VARS_PLUGIN_STAGE +''' + +from ansible.plugins.vars import BaseVarsPlugin + + +class VarsModule(BaseVarsPlugin): + + def get_vars(self, loader, path, entities, cache=True): + super(VarsModule, self).get_vars(loader, path, entities) + return {'collection': False, 'name': 'v2_vars_plugin', 'v2_vars_plugin': True} diff --git a/test/integration/targets/collections/custom_vars_plugins/vars_req_whitelist.py b/test/integration/targets/collections/custom_vars_plugins/vars_req_whitelist.py new file mode 100644 index 00000000..0ab95273 --- /dev/null +++ b/test/integration/targets/collections/custom_vars_plugins/vars_req_whitelist.py @@ -0,0 +1,46 @@ +# Copyright 2019 RedHat, inc +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. +############################################# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' + vars: vars_req_whitelist + version_added: "2.10" + short_description: load host and group vars + description: test loading host and group vars from a collection + options: + stage: + choices: ['all', 'inventory', 'task'] + type: str + ini: + - key: stage + section: vars_req_whitelist + env: + - name: ANSIBLE_VARS_PLUGIN_STAGE +''' + +from ansible.plugins.vars import BaseVarsPlugin + + +class VarsModule(BaseVarsPlugin): + + REQUIRES_WHITELIST = True + + def get_vars(self, loader, path, entities, cache=True): + super(VarsModule, self).get_vars(loader, path, entities) + return {'whitelisted': True, 'collection': False} diff --git a/test/integration/targets/collections/filter_plugins/override_formerly_core_masked_filter.py b/test/integration/targets/collections/filter_plugins/override_formerly_core_masked_filter.py new file mode 100644 index 00000000..600b1fd8 --- /dev/null +++ b/test/integration/targets/collections/filter_plugins/override_formerly_core_masked_filter.py @@ -0,0 +1,13 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def override_formerly_core_masked_filter(*args, **kwargs): + return 'hello from overridden formerly_core_masked_filter' + + +class FilterModule(object): + def filters(self): + return { + 'formerly_core_masked_filter': override_formerly_core_masked_filter + } diff --git a/test/integration/targets/collections/includeme.yml b/test/integration/targets/collections/includeme.yml new file mode 100644 index 00000000..219ee58f --- /dev/null +++ b/test/integration/targets/collections/includeme.yml @@ -0,0 +1,6 @@ +- testns.testcoll.plugin_lookup: + register: included_plugin_lookup_out + +- assert: + that: + - included_plugin_lookup_out.collection_list == ['bogus.bogus', 'ansible.legacy'] diff --git a/test/integration/targets/collections/inventory_test.yml b/test/integration/targets/collections/inventory_test.yml new file mode 100644 index 00000000..b5089278 --- /dev/null +++ b/test/integration/targets/collections/inventory_test.yml @@ -0,0 +1,26 @@ +- name: test a collection-hosted connection plugin against hosts from collection-hosted inventory plugins + hosts: dynamic_host_a, dynamic_host_redirected + gather_facts: no + vars: + ansible_connection: testns.testcoll.localconn + ansible_localconn_connectionvar: from_play + tasks: + - raw: echo 'hello world' + register: connection_out + + - assert: + that: + - connection_out.stdout == "localconn ran echo 'hello world'" + # ensure that the connection var we overrode above made it into the running config + - connection_out.stderr == "connectionvar is from_play" + + +- hosts: localhost + gather_facts: no + tasks: + - assert: + that: + - hostvars['dynamic_host_a'] is defined + - hostvars['dynamic_host_a'].connection_out.stdout == "localconn ran echo 'hello world'" + - hostvars['dynamic_host_redirected'] is defined + - hostvars['dynamic_host_redirected'].connection_out.stdout == "localconn ran echo 'hello world'" diff --git a/test/integration/targets/collections/invocation_tests.yml b/test/integration/targets/collections/invocation_tests.yml new file mode 100644 index 00000000..c80e1edc --- /dev/null +++ b/test/integration/targets/collections/invocation_tests.yml @@ -0,0 +1,5 @@ +- hosts: testhost + gather_facts: false + tasks: + - name: run action that invokes module from another collection + me.mycoll1.action1: desc="this should run me.mycoll2.module1" diff --git a/test/integration/targets/collections/library/ping.py b/test/integration/targets/collections/library/ping.py new file mode 100644 index 00000000..7a416a64 --- /dev/null +++ b/test/integration/targets/collections/library/ping.py @@ -0,0 +1,13 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + + +def main(): + print(json.dumps(dict(changed=False, source='legacy_library_dir'))) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/noop.yml b/test/integration/targets/collections/noop.yml new file mode 100644 index 00000000..81c6e473 --- /dev/null +++ b/test/integration/targets/collections/noop.yml @@ -0,0 +1,4 @@ +- hosts: localhost + gather_facts: no + tasks: + - debug: diff --git a/test/integration/targets/collections/posix.yml b/test/integration/targets/collections/posix.yml new file mode 100644 index 00000000..903fb4ff --- /dev/null +++ b/test/integration/targets/collections/posix.yml @@ -0,0 +1,443 @@ +- hosts: testhost + tasks: + # basic test of FQ module lookup and that we got the right one (user-dir hosted) + - name: exec FQ module in a user-dir testns collection + testns.testcoll.testmodule: + register: testmodule_out + + # verifies that distributed collection subpackages are visible under a multi-location namespace (testns exists in user and sys locations) + - name: exec FQ module in a sys-dir testns collection + testns.coll_in_sys.systestmodule: + register: systestmodule_out + + # verifies that content-adjacent collections were automatically added to the installed content roots + - name: exec FQ module from content-adjacent collection + testns.content_adj.contentadjmodule: + register: contentadjmodule_out + + # content should only be loaded from the first visible instance of a collection + - name: attempt to look up FQ module in a masked collection + testns.testcoll.plugin_lookup: + type: module + name: testns.testcoll.maskedmodule + register: maskedmodule_out + + # ensure the ansible ns can have real collections added to it + - name: call an external module in the ansible namespace + ansible.bullcoll.bullmodule: + register: bullmodule_out + + # ensure the ansible ns cannot override ansible.builtin externally + - name: call an external module in the ansible.builtin collection (should use the built in module) + ansible.builtin.ping: + register: builtin_ping_out + + # action in a collection subdir + - name: test subdir action FQ + testns.testcoll.action_subdir.subdir_ping_action: + register: subdir_ping_action_out + + # module in a collection subdir + - name: test subdir module FQ + testns.testcoll.module_subdir.subdir_ping_module: + register: subdir_ping_module_out + + # module with a granular module_utils import (from (this collection).module_utils.leaf import thingtocall) + - name: exec module with granular module utils import from this collection + testns.testcoll.uses_leaf_mu_granular_import: + register: granular_out + + # module with a granular nested module_utils import (from (this collection).module_utils.base import thingtocall, + # where base imports secondary from the same collection's module_utils) + - name: exec module with nested module utils from this collection + testns.testcoll.uses_base_mu_granular_nested_import: + register: granular_nested_out + + # module with a flat module_utils import (import (this collection).module_utils.leaf) + - name: exec module with flat module_utils import from this collection + testns.testcoll.uses_leaf_mu_flat_import: + register: flat_out + + # module with a full-module module_utils import using 'from' (from (this collection).module_utils import leaf) + - name: exec module with full-module module_utils import using 'from' from this collection + testns.testcoll.uses_leaf_mu_module_import_from: + register: from_out + + # module with multiple levels of the same nested package name and imported as a function + - name: exec module with multiple levels of the same nested package name imported as a function + testns.testcoll.uses_nested_same_as_func: + register: from_nested_func + + # module with multiple levels of the same nested package name and imported as a module + - name: exec module with multiple levels of the same nested package name imported as a module + testns.testcoll.uses_nested_same_as_module: + register: from_nested_module + + # module using a bunch of collection-level redirected module_utils + - name: exec module using a bunch of collection-level redirected module_utils + testns.testcoll.uses_collection_redirected_mu: + register: from_redirected_mu + + # module with bogus MU + - name: exec module with bogus MU + testns.testcoll.uses_mu_missing: + ignore_errors: true + register: from_missing_mu + + # module with redirected MU, redirect collection not found + - name: exec module with a missing redirect target collection + testns.testcoll.uses_mu_missing_redirect_collection: + ignore_errors: true + register: from_missing_redir_collection + + # module with redirected MU, redirect module not found + - name: exec module with a missing redirect target module + testns.testcoll.uses_mu_missing_redirect_module: + ignore_errors: true + register: from_missing_redir_module + + - assert: + that: + - testmodule_out.source == 'user' + - systestmodule_out.source == 'sys' + - contentadjmodule_out.source == 'content_adj' + - not maskedmodule_out.plugin_path + - bullmodule_out.source == 'user_ansible_bullcoll' + - builtin_ping_out.source is not defined + - builtin_ping_out.ping == 'pong' + - subdir_ping_action_out is not changed + - subdir_ping_module_out is not changed + - granular_out.mu_result == 'thingtocall in leaf' + - granular_nested_out.mu_result == 'thingtocall in base called thingtocall in secondary' + - flat_out.mu_result == 'thingtocall in leaf' + - from_out.mu_result == 'thingtocall in leaf' + - from_out.mu2_result == 'thingtocall in secondary' + - from_out.mu3_result == 'thingtocall in subpkg.submod' + - from_out.mu4_result == 'thingtocall in subpkg_with_init' + - from_out.mu5_result == 'thingtocall in mod_in_subpkg_with_init' + - from_out.mu6_result == 'thingtocall in subpkg.submod' + - from_nested_func.mu_result == 'hello from nested_same' + - from_nested_module.mu_result == 'hello from nested_same' + - from_redirected_mu.mu_result == 'hello from ansible_collections.testns.content_adj.plugins.module_utils.sub1.foomodule' + - from_redirected_mu.mu_result2 == 'hello from testns.othercoll.formerly_testcoll_pkg.thing' + - from_redirected_mu.mu_result3 == 'hello from formerly_testcoll_pkg.submod.thing' + - from_missing_mu is failed + - "'Could not find imported module support' in from_missing_mu.msg" + - from_missing_redir_collection is failed + - "'unable to locate collection bogusns.boguscoll' in from_missing_redir_collection.msg" + - from_missing_redir_module is failed + - "'Could not find imported module support code for ansible_collections.testns.testcoll.plugins.modules.uses_mu_missing_redirect_module' in from_missing_redir_module.msg" + + +- hosts: testhost + tasks: + - name: exercise filters/tests/lookups + assert: + that: + - "'data' | testns.testcoll.testfilter == 'data_via_testfilter_from_userdir'" + - "'data' | testns.testcoll.testfilter2 == 'data_via_testfilter2_from_userdir'" + - "'data' | testns.testcoll.filter_subdir.test_subdir_filter == 'data_via_testfilter_from_subdir'" + - "'from_user' is testns.testcoll.testtest" + - "'from_user2' is testns.testcoll.testtest2" + - "'subdir_from_user' is testns.testcoll.test_subdir.subdir_test" + - lookup('testns.testcoll.mylookup') == 'mylookup_from_user_dir' + - lookup('testns.testcoll.mylookup2') == 'mylookup2_from_user_dir' + - lookup('testns.testcoll.lookup_subdir.my_subdir_lookup') == 'subdir_lookup_from_user_dir' + + - debug: + msg: "{{ 'foo'|testns.testbroken.broken }}" + register: result + ignore_errors: true + + - assert: + that: + - | + 'This is a broken filter plugin.' in result.msg + + - debug: + msg: "{{ 'foo'|missing.collection.filter }}" + register: result + ignore_errors: true + + - assert: + that: + - result is failed + +# ensure that the synthetic ansible.builtin collection limits to builtin plugins, that ansible.legacy loads overrides +# from legacy plugin dirs, and that a same-named plugin loaded from a real collection is not masked by the others +- hosts: testhost + tasks: + - name: test unqualified ping from library dir + ping: + register: unqualified_ping_out + + - name: test legacy-qualified ping from library dir + ansible.legacy.ping: + register: legacy_ping_out + + - name: test builtin ping + ansible.builtin.ping: + register: builtin_ping_out + + - name: test collection-based ping + testns.testcoll.ping: + register: collection_ping_out + + - assert: + that: + - unqualified_ping_out.source == 'legacy_library_dir' + - legacy_ping_out.source == 'legacy_library_dir' + - builtin_ping_out.ping == 'pong' + - collection_ping_out.source == 'user' + +# verify the default value for the collections list is empty +- hosts: testhost + tasks: + - name: sample default collections value + testns.testcoll.plugin_lookup: + register: coll_default_out + + - assert: + that: + # in original release, collections defaults to empty, which is mostly equivalent to ansible.legacy + - not coll_default_out.collection_list + + +# ensure that inheritance/masking works as expected, that the proper default values are injected when missing, +# and that the order is preserved if one of the magic values is explicitly specified +- name: verify collections keyword play/block/task inheritance and magic values + hosts: testhost + collections: + - bogus.fromplay + tasks: + - name: sample play collections value + testns.testcoll.plugin_lookup: + register: coll_play_out + + - name: collections override block-level + collections: + - bogus.fromblock + block: + - name: sample block collections value + testns.testcoll.plugin_lookup: + register: coll_block_out + + - name: sample task collections value + collections: + - bogus.fromtask + testns.testcoll.plugin_lookup: + register: coll_task_out + + - name: sample task with explicit core + collections: + - ansible.builtin + - bogus.fromtaskexplicitcore + testns.testcoll.plugin_lookup: + register: coll_task_core + + - name: sample task with explicit legacy + collections: + - ansible.legacy + - bogus.fromtaskexplicitlegacy + testns.testcoll.plugin_lookup: + register: coll_task_legacy + + - assert: + that: + # ensure that parent value inheritance is masked properly by explicit setting + - coll_play_out.collection_list == ['bogus.fromplay', 'ansible.legacy'] + - coll_block_out.collection_list == ['bogus.fromblock', 'ansible.legacy'] + - coll_task_out.collection_list == ['bogus.fromtask', 'ansible.legacy'] + - coll_task_core.collection_list == ['ansible.builtin', 'bogus.fromtaskexplicitcore'] + - coll_task_legacy.collection_list == ['ansible.legacy', 'bogus.fromtaskexplicitlegacy'] + +- name: verify unqualified plugin resolution behavior + hosts: testhost + collections: + - testns.testcoll + - testns.coll_in_sys + - testns.contentadj + tasks: + # basic test of unqualified module lookup and that we got the right one (user-dir hosted, there's another copy of + # this one in the same-named collection in sys dir that should be masked + - name: exec unqualified module in a user-dir testns collection + testmodule: + register: testmodule_out + + # use another collection to verify that we're looking in all collections listed on the play + - name: exec unqualified module in a sys-dir testns collection + systestmodule: + register: systestmodule_out + + - assert: + that: + - testmodule_out.source == 'user' + - systestmodule_out.source == 'sys' + +# test keyword-static execution of a FQ collection-backed role with "tasks/main.yaml" +- name: verify collection-backed role execution (keyword static) + hosts: testhost + collections: + # set to ansible.builtin only to ensure that roles function properly without inheriting the play's collections config + - ansible.builtin + vars: + test_role_input: keyword static + roles: + - role: testns.testcoll.testrole_main_yaml + tasks: + - name: ensure role executed + assert: + that: + - test_role_output.msg == test_role_input + - testrole_source == 'collection' + + +# test dynamic execution of a FQ collection-backed role +- name: verify collection-backed role execution (dynamic) + hosts: testhost + collections: + # set to ansible.builtin only to ensure that roles function properly without inheriting the play's collections config + - ansible.builtin + vars: + test_role_input: dynamic + tasks: + - include_role: + name: testns.testcoll.testrole + - name: ensure role executed + assert: + that: + - test_role_output.msg == test_role_input + - testrole_source == 'collection' + +# test task-static execution of a FQ collection-backed role +- name: verify collection-backed role execution (task static) + hosts: testhost + collections: + - ansible.builtin + vars: + test_role_input: task static + tasks: + - import_role: + name: testns.testcoll.testrole + - name: ensure role executed + assert: + that: + - test_role_output.msg == test_role_input + - testrole_source == 'collection' + + +# test a legacy playbook-adjacent role, ensure that play collections config is not inherited +- name: verify legacy playbook-adjacent role behavior + hosts: testhost + collections: + - bogus.bogus + vars: + test_role_input: legacy playbook-adjacent + roles: + - testrole +# FIXME: this should technically work to look up a playbook-adjacent role +# - ansible.legacy.testrole + tasks: + - name: ensure role executed + assert: + that: + - test_role_output.msg == test_role_input + - testrole_source == 'legacy roles dir' + + +# test dynamic execution of a FQ collection-backed role +- name: verify collection-backed role execution in subdir (include) + hosts: testhost + vars: + test_role_input: dynamic (subdir) + tasks: + - include_role: + name: testns.testcoll.role_subdir.subdir_testrole + - name: ensure role executed + assert: + that: + - test_role_output.msg == test_role_input + - testrole_source == 'collection' + + +# test collection-relative role deps (keyword static) +- name: verify collection-relative role deps + hosts: testhost + vars: + outer_role_input: keyword static outer + test_role_input: keyword static inner + roles: + - testns.testcoll.calls_intra_collection_dep_role_unqualified + tasks: + - assert: + that: + - outer_role_output.msg == outer_role_input + - test_role_output.msg == test_role_input + - testrole_source == 'collection' + +# test collection-relative role deps (task static) +- name: verify collection-relative role deps + hosts: testhost + vars: + outer_role_input: task static outer + test_role_input: task static inner + tasks: + - import_role: + name: testns.testcoll.calls_intra_collection_dep_role_unqualified + - assert: + that: + - outer_role_output.msg == outer_role_input + - test_role_output.msg == test_role_input + - testrole_source == 'collection' + +# test collection-relative role deps (task dynamic) +- name: verify collection-relative role deps + hosts: testhost + vars: + outer_role_input: task dynamic outer + test_role_input: task dynamic inner + tasks: + - include_role: + name: testns.testcoll.calls_intra_collection_dep_role_unqualified + - assert: + that: + - outer_role_output.msg == outer_role_input + - test_role_output.msg == test_role_input + - testrole_source == 'collection' + + +- name: validate static task include behavior + hosts: testhost + collections: + - bogus.bogus + tasks: + - import_tasks: includeme.yml + + +- name: validate dynamic task include behavior + hosts: testhost + collections: + - bogus.bogus + tasks: + - include_tasks: includeme.yml + + +- import_playbook: test_collection_meta.yml +- name: Test FQCN handlers + hosts: testhost + vars: + handler_counter: 0 + roles: + - testns.testcoll.test_fqcn_handlers + +- name: Ensure a collection role can call a standalone role + hosts: testhost + roles: + - testns.testcoll.call_standalone + +# Issue https://github.com/ansible/ansible/issues/69054 +- name: Test collection as string + hosts: testhost + collections: foo + tasks: + - debug: msg="Test" diff --git a/test/integration/targets/collections/redirected.statichost.yml b/test/integration/targets/collections/redirected.statichost.yml new file mode 100644 index 00000000..9fd2c2d8 --- /dev/null +++ b/test/integration/targets/collections/redirected.statichost.yml @@ -0,0 +1,3 @@ +# use a plugin redirected by core to a collection to ensure inventory redirection and redirected config names are working +plugin: formerly_core_inventory # this is defined in the ansible-base runtime.yml routing to point at testns.content_adj.statichost +hostname: dynamic_host_redirected diff --git a/test/integration/targets/collections/roles/standalone/tasks/main.yml b/test/integration/targets/collections/roles/standalone/tasks/main.yml new file mode 100644 index 00000000..b4dd23db --- /dev/null +++ b/test/integration/targets/collections/roles/standalone/tasks/main.yml @@ -0,0 +1,2 @@ +- set_fact: + standalone_role_var: True diff --git a/test/integration/targets/collections/roles/testrole/tasks/main.yml b/test/integration/targets/collections/roles/testrole/tasks/main.yml new file mode 100644 index 00000000..cbf6b8e7 --- /dev/null +++ b/test/integration/targets/collections/roles/testrole/tasks/main.yml @@ -0,0 +1,28 @@ +- debug: + msg: executing testrole from legacy playbook-adjacent roles dir + +- name: exec a FQ module from a legacy role + testns.testcoll.testmodule: + register: coll_module_out + +- name: exec a legacy playbook-adjacent module from a legacy role + ping: + register: ping_out + +- name: sample collections list inside a legacy role (should be empty) + testns.testcoll.plugin_lookup: + register: plugin_lookup_out + +- debug: + msg: '{{ test_role_input | default("(undefined)") }}' + register: test_role_output + +- set_fact: + testrole_source: legacy roles dir + +- assert: + that: + - coll_module_out.source == 'user' + # ensure we used the library/ ping override, not the builtin or one from another collection + - ping_out.source == 'legacy_library_dir' + - not plugin_lookup_out.collection_list diff --git a/test/integration/targets/collections/runme.sh b/test/integration/targets/collections/runme.sh new file mode 100755 index 00000000..f3e886a5 --- /dev/null +++ b/test/integration/targets/collections/runme.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash + +set -eux + +export ANSIBLE_COLLECTIONS_PATH=$PWD/collection_root_user:$PWD/collection_root_sys +export ANSIBLE_GATHERING=explicit +export ANSIBLE_GATHER_SUBSET=minimal +export ANSIBLE_HOST_PATTERN_MISMATCH=error + +# FUTURE: just use INVENTORY_PATH as-is once ansible-test sets the right dir +ipath=../../$(basename "${INVENTORY_PATH:-../../inventory}") +export INVENTORY_PATH="$ipath" + +echo "--- validating callbacks" +# validate FQ callbacks in ansible-playbook +ANSIBLE_CALLBACK_WHITELIST=testns.testcoll.usercallback ansible-playbook noop.yml | grep "usercallback says ok" +# use adhoc for the rest of these tests, must force it to load other callbacks +export ANSIBLE_LOAD_CALLBACK_PLUGINS=1 +# validate redirected callback +ANSIBLE_CALLBACK_WHITELIST=formerly_core_callback ansible localhost -m debug 2>&1 | grep -- "usercallback says ok" +## validate missing redirected callback +ANSIBLE_CALLBACK_WHITELIST=formerly_core_missing_callback ansible localhost -m debug 2>&1 | grep -- "Skipping callback plugin 'formerly_core_missing_callback'" +## validate redirected + removed callback (fatal) +ANSIBLE_CALLBACK_WHITELIST=formerly_core_removed_callback ansible localhost -m debug 2>&1 | grep -- "testns.testcoll.removedcallback has been removed" +# validate avoiding duplicate loading of callback, even if using diff names +[ "$(ANSIBLE_CALLBACK_WHITELIST=testns.testcoll.usercallback,formerly_core_callback ansible localhost -m debug 2>&1 | grep -c 'usercallback says ok')" = "1" ] +# ensure non existing callback does not crash ansible +ANSIBLE_CALLBACK_WHITELIST=charlie.gomez.notme ansible localhost -m debug 2>&1 | grep -- "Skipping callback plugin 'charlie.gomez.notme'" +unset ANSIBLE_LOAD_CALLBACK_PLUGINS +# adhoc normally shouldn't load non-default plugins- let's be sure +output=$(ANSIBLE_CALLBACK_WHITELIST=testns.testcoll.usercallback ansible localhost -m debug) +if [[ "${output}" =~ "usercallback says ok" ]]; then echo fail; exit 1; fi + +echo "--- validating docs" +# test documentation +ansible-doc testns.testcoll.testmodule -vvv | grep -- "- normal_doc_frag" +# same with symlink +ln -s "${PWD}/testcoll2" ./collection_root_sys/ansible_collections/testns/testcoll2 +ansible-doc testns.testcoll2.testmodule2 -vvv | grep "Test module" +# now test we can list with symlink +ansible-doc -l -vvv| grep "testns.testcoll2.testmodule2" + +echo "testing bad doc_fragments (expected ERROR message follows)" +# test documentation failure +ansible-doc testns.testcoll.testmodule_bad_docfrags -vvv 2>&1 | grep -- "unknown doc_fragment" + +echo "--- validating default collection" +# test adhoc default collection resolution (use unqualified collection module with playbook dir under its collection) + +echo "testing adhoc default collection support with explicit playbook dir" +ANSIBLE_PLAYBOOK_DIR=./collection_root_user/ansible_collections/testns/testcoll ansible localhost -m testmodule + +# we need multiple plays, and conditional import_playbook is noisy and causes problems, so choose here which one to use... +if [[ ${INVENTORY_PATH} == *.winrm ]]; then + export TEST_PLAYBOOK=windows.yml +else + export TEST_PLAYBOOK=posix.yml + + echo "testing default collection support" + ansible-playbook -i "${INVENTORY_PATH}" collection_root_user/ansible_collections/testns/testcoll/playbooks/default_collection_playbook.yml "$@" +fi + +echo "--- validating collections support in playbooks/roles" +# run test playbooks +ansible-playbook -i "${INVENTORY_PATH}" -v "${TEST_PLAYBOOK}" "$@" + +if [[ ${INVENTORY_PATH} != *.winrm ]]; then + ansible-playbook -i "${INVENTORY_PATH}" -v invocation_tests.yml "$@" +fi + +echo "--- validating bypass_host_loop with collection search" +ansible-playbook -i host1,host2, -v test_bypass_host_loop.yml "$@" + +echo "--- validating inventory" +# test collection inventories +ansible-playbook inventory_test.yml -i a.statichost.yml -i redirected.statichost.yml "$@" + +# test plugin loader redirect_list +ansible-playbook test_redirect_list.yml -v "$@" + +# test adjacent with --playbook-dir +export ANSIBLE_COLLECTIONS_PATH='' +ANSIBLE_INVENTORY_ANY_UNPARSED_IS_FAILED=1 ansible-inventory --list --export --playbook-dir=. -v "$@" + +# use an inventory source with caching enabled +ansible-playbook -i a.statichost.yml -i ./cache.statichost.yml -v check_populated_inventory.yml + +# Check that the inventory source with caching enabled was stored +if [[ "$(find ./inventory_cache -type f ! -path "./inventory_cache/.keep" | wc -l)" -ne "1" ]]; then + echo "Failed to find the expected single cache" + exit 1 +fi + +CACHEFILE="$(find ./inventory_cache -type f ! -path './inventory_cache/.keep')" + +if [[ $CACHEFILE != ./inventory_cache/prefix_* ]]; then + echo "Unexpected cache file" + exit 1 +fi + +# Check the cache for the expected hosts + +if [[ "$(grep -wc "cache_host_a" "$CACHEFILE")" -ne "1" ]]; then + echo "Failed to cache host as expected" + exit 1 +fi + +if [[ "$(grep -wc "dynamic_host_a" "$CACHEFILE")" -ne "0" ]]; then + echo "Cached an incorrect source" + exit 1 +fi + +./vars_plugin_tests.sh + diff --git a/test/integration/targets/collections/test_bypass_host_loop.yml b/test/integration/targets/collections/test_bypass_host_loop.yml new file mode 100644 index 00000000..e95262b8 --- /dev/null +++ b/test/integration/targets/collections/test_bypass_host_loop.yml @@ -0,0 +1,22 @@ +- name: Test collection lookup bypass host list + hosts: all + connection: local + gather_facts: false + collections: + - testns.testcoll + tasks: + - meta: end_host + when: lookup('pipe', ansible_playbook_python ~ ' -c "import jinja2; print(jinja2.__version__)"') is version('2.7', '<') + + - bypass_host_loop: + register: bypass + + - run_once: true + vars: + bypass_hosts: '{{ hostvars|dictsort|map(attribute="1.bypass.bypass_inventory_hostname")|select("defined")|unique }}' + block: + - debug: + var: bypass_hosts + + - assert: + that: bypass_hosts|length == 1 diff --git a/test/integration/targets/collections/test_collection_meta.yml b/test/integration/targets/collections/test_collection_meta.yml new file mode 100644 index 00000000..22a00b21 --- /dev/null +++ b/test/integration/targets/collections/test_collection_meta.yml @@ -0,0 +1,46 @@ +- hosts: localhost + gather_facts: no + collections: + - testns.testcoll + vars: + # redirect connection + ansible_connection: testns.testcoll.redirected_local + tasks: + - assert: + that: ('data' | testns.testcoll.testfilter) == 'data_via_testfilter_from_userdir' + + # redirect module (multiple levels) + - multilevel1: + # redirect action + - uses_redirected_action: + # redirect import (consumed via action) + - uses_redirected_import: + # redirect lookup + - assert: + that: lookup('formerly_core_lookup') == 'mylookup_from_user_dir' + # redirect filter + - assert: + that: ('yes' | formerly_core_filter) == True + # legacy filter should mask redirected + - assert: + that: ('' | formerly_core_masked_filter) == 'hello from overridden formerly_core_masked_filter' + # redirect test + - assert: + that: + - "'stuff' is formerly_core_test('tuf')" + - "'hello override' is formerly_core_masked_test" + # redirect module (formerly internal) + - formerly_core_ping: + # redirect module from collection (with subdir) + - testns.testcoll.module_subdir.subdir_ping_module: + # redirect module_utils plugin (consumed via module) + - uses_core_redirected_mu: + # deprecated module (issues warning) + - deprecated_ping: + # redirect module (internal alias) + - aliased_ping: + # redirect module (cycle detection, fatal) +# - looped_ping: + + # removed module (fatal) +# - dead_ping: diff --git a/test/integration/targets/collections/test_plugins/override_formerly_core_masked_test.py b/test/integration/targets/collections/test_plugins/override_formerly_core_masked_test.py new file mode 100644 index 00000000..11c7f7a7 --- /dev/null +++ b/test/integration/targets/collections/test_plugins/override_formerly_core_masked_test.py @@ -0,0 +1,16 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def override_formerly_core_masked_test(value, *args, **kwargs): + if value != 'hello override': + raise Exception('expected "hello override" only...') + + return True + + +class TestModule(object): + def tests(self): + return { + 'formerly_core_masked_test': override_formerly_core_masked_test + } diff --git a/test/integration/targets/collections/test_redirect_list.yml b/test/integration/targets/collections/test_redirect_list.yml new file mode 100644 index 00000000..8a24b960 --- /dev/null +++ b/test/integration/targets/collections/test_redirect_list.yml @@ -0,0 +1,86 @@ +--- +- hosts: localhost + gather_facts: no + module_defaults: + testns.testcoll.plugin_lookup: + type: module + tasks: + - name: test builtin + testns.testcoll.plugin_lookup: + name: dnf + register: result + failed_when: + - result['redirect_list'] != ['dnf'] or result['plugin_path'].endswith('library/dnf.py') + + - name: test builtin with collections kw + testns.testcoll.plugin_lookup: + name: dnf + register: result + failed_when: + - result['redirect_list'] != ['dnf'] or result['plugin_path'].endswith('library/dnf.py') + collections: + - testns.unrelatedcoll + + - name: test redirected builtin + testns.testcoll.plugin_lookup: + name: formerly_core_ping + register: result + failed_when: result['redirect_list'] != expected_redirect_list + vars: + expected_redirect_list: + - formerly_core_ping + - ansible.builtin.formerly_core_ping + - testns.testcoll.ping + + - name: test redirected builtin with collections kw + testns.testcoll.plugin_lookup: + name: formerly_core_ping + register: result + failed_when: result['redirect_list'] != expected_redirect_list + vars: + expected_redirect_list: + - formerly_core_ping + - ansible.builtin.formerly_core_ping + - testns.testcoll.ping + collections: + - testns.unrelatedcoll + - testns.testcoll + + - name: test collection module with collections kw + testns.testcoll.plugin_lookup: + name: ping + register: result + failed_when: result['redirect_list'] != expected_redirect_list + vars: + expected_redirect_list: + - ping + - testns.testcoll.ping + collections: + - testns.unrelatedcoll + - testns.testcoll + + - name: test redirected collection module with collections kw + testns.testcoll.plugin_lookup: + name: ping + register: result + failed_when: result['redirect_list'] != expected_redirect_list + vars: + expected_redirect_list: + - ping + - testns.testredirect.ping + - testns.testcoll.ping + collections: + - testns.unrelatedcoll + - testns.testredirect + + - name: test legacy module with collections kw + testns.testcoll.plugin_lookup: + name: ping + register: result + failed_when: + - result['redirect_list'] != expected_redirect_list or not result['plugin_path'].endswith('library/ping.py') + vars: + expected_redirect_list: + - ping + collections: + - testns.unrelatedcoll diff --git a/test/integration/targets/collections/testcoll2/MANIFEST.json b/test/integration/targets/collections/testcoll2/MANIFEST.json new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/integration/targets/collections/testcoll2/MANIFEST.json diff --git a/test/integration/targets/collections/testcoll2/plugins/modules/testmodule2.py b/test/integration/targets/collections/testcoll2/plugins/modules/testmodule2.py new file mode 100644 index 00000000..7f6eb024 --- /dev/null +++ b/test/integration/targets/collections/testcoll2/plugins/modules/testmodule2.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['stableinterface'], + 'supported_by': 'core'} + +DOCUMENTATION = ''' +--- +module: testmodule2 +short_description: Test module +description: + - Test module +author: + - Ansible Core Team +''' + +EXAMPLES = ''' +''' + +RETURN = ''' +''' + +import json + + +def main(): + print(json.dumps(dict(changed=False, source='sys'))) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/vars_plugin_tests.sh b/test/integration/targets/collections/vars_plugin_tests.sh new file mode 100755 index 00000000..2118af6d --- /dev/null +++ b/test/integration/targets/collections/vars_plugin_tests.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash + +set -eux + +# Collections vars plugins must be whitelisted with FQCN because PluginLoader.all() does not search collections + +# Let vars plugins run for inventory by using the global setting +export ANSIBLE_RUN_VARS_PLUGINS=start + +# Test vars plugin in a playbook-adjacent collection +export ANSIBLE_VARS_ENABLED=testns.content_adj.custom_adj_vars + +ansible-inventory -i a.statichost.yml --list --playbook-dir=./ | tee out.txt + +grep '"collection": "adjacent"' out.txt +grep '"adj_var": "value"' out.txt + +# Test vars plugin in a collection path +export ANSIBLE_VARS_ENABLED=testns.testcoll.custom_vars +export ANSIBLE_COLLECTIONS_PATH=$PWD/collection_root_user:$PWD/collection_root_sys + +ansible-inventory -i a.statichost.yml --list --playbook-dir=./ | tee out.txt + +grep '"collection": "collection_root_user"' out.txt +grep -v '"adj_var": "value"' out.txt + +# Test enabled vars plugins order reflects the order in which variables are merged +export ANSIBLE_VARS_ENABLED=testns.content_adj.custom_adj_vars,testns.testcoll.custom_vars + +ansible-inventory -i a.statichost.yml --list --playbook-dir=./ | tee out.txt + +grep '"collection": "collection_root_user"' out.txt +grep '"adj_var": "value"' out.txt +grep -v '"collection": "adjacent"' out.txt + +# Test that 3rd party plugins in plugin_path do not need to require whitelisting by default +# Plugins shipped with Ansible and in the custom plugin dir should be used first +export ANSIBLE_VARS_PLUGINS=./custom_vars_plugins + +ansible-inventory -i a.statichost.yml --list --playbook-dir=./ | tee out.txt + +grep '"name": "v2_vars_plugin"' out.txt +grep '"collection": "collection_root_user"' out.txt +grep '"adj_var": "value"' out.txt +grep -v '"whitelisted": true' out.txt + +# Test plugins in plugin paths that opt-in to require whitelisting +unset ANSIBLE_VARS_ENABLED +unset ANSIBLE_COLLECTIONS_PATH + +ANSIBLE_VARS_ENABLED=vars_req_whitelist ansible-inventory -i a.statichost.yml --list --playbook-dir=./ | tee out.txt + +grep '"whitelisted": true' out.txt + +# Test vars plugins that support the stage setting don't run for inventory when stage is set to 'task' +# and that the vars plugins that don't support the stage setting don't run for inventory when the global setting is 'demand' +ANSIBLE_VARS_PLUGIN_STAGE=task ansible-inventory -i a.statichost.yml --list --playbook-dir=./ | tee out.txt + +grep -v '"v1_vars_plugin": true' out.txt +grep -v '"v2_vars_plugin": true' out.txt +grep -v '"vars_req_whitelist": true' out.txt +grep -v '"collection": "adjacent"' out.txt +grep -v '"collection": "collection_root_user"' out.txt +grep -v '"adj_var": "value"' out.txt + +# Test that the global setting allows v1 and v2 plugins to run after importing inventory +ANSIBLE_RUN_VARS_PLUGINS=start ansible-inventory -i a.statichost.yml --list --playbook-dir=./ | tee out.txt + +grep -v '"vars_req_whitelist": true' out.txt +grep '"v1_vars_plugin": true' out.txt +grep '"v2_vars_plugin": true' out.txt +grep '"name": "v2_vars_plugin"' out.txt + +# Test that vars plugins in collections and in the vars plugin path are available for tasks +cat << EOF > "test_task_vars.yml" +--- +- hosts: localhost + connection: local + gather_facts: no + tasks: + - debug: msg="{{ name }}" + - debug: msg="{{ collection }}" + - debug: msg="{{ adj_var }}" +EOF + +export ANSIBLE_VARS_ENABLED=testns.content_adj.custom_adj_vars + +ANSIBLE_VARS_PLUGIN_STAGE=task ANSIBLE_VARS_PLUGINS=./custom_vars_plugins ansible-playbook test_task_vars.yml | grep "ok=3" +ANSIBLE_RUN_VARS_PLUGINS=start ANSIBLE_VARS_PLUGIN_STAGE=inventory ANSIBLE_VARS_PLUGINS=./custom_vars_plugins ansible-playbook test_task_vars.yml | grep "ok=3" +ANSIBLE_RUN_VARS_PLUGINS=demand ANSIBLE_VARS_PLUGIN_STAGE=inventory ANSIBLE_VARS_PLUGINS=./custom_vars_plugins ansible-playbook test_task_vars.yml | grep "ok=3" +ANSIBLE_VARS_PLUGINS=./custom_vars_plugins ansible-playbook test_task_vars.yml | grep "ok=3" diff --git a/test/integration/targets/collections/windows.yml b/test/integration/targets/collections/windows.yml new file mode 100644 index 00000000..4bdfb0ed --- /dev/null +++ b/test/integration/targets/collections/windows.yml @@ -0,0 +1,28 @@ +- hosts: windows + tasks: + - testns.testcoll.win_selfcontained: + register: selfcontained_out + + - testns.testcoll.win_csbasic_only: + register: csbasic_only_out + + - testns.testcoll.win_uses_coll_psmu: + register: uses_coll_psmu + + - testns.testcoll.win_uses_coll_csmu: + register: uses_coll_csmu + + - assert: + that: + - selfcontained_out.source == 'user' + - csbasic_only_out.source == 'user' + # win_uses_coll_psmu + - uses_coll_psmu.source == 'user' + - "'user_mu' in uses_coll_psmu.ping" + - uses_coll_psmu.subpkg == 'from subpkg.subps.psm1' + # win_uses_coll_csmu + - uses_coll_csmu.source == 'user' + - "'user_mu' in uses_coll_csmu.ping" + - "'Hello from subpkg.subcs' in uses_coll_csmu.ping" + - uses_coll_csmu.subpkg == 'Hello from subpkg.subcs' + - uses_coll_csmu.type_accelerator == uses_coll_csmu.ping |