diff options
Diffstat (limited to '')
170 files changed, 3514 insertions, 0 deletions
diff --git a/test/integration/targets/collection/aliases b/test/integration/targets/collection/aliases new file mode 100644 index 0000000..136c05e --- /dev/null +++ b/test/integration/targets/collection/aliases @@ -0,0 +1 @@ +hidden diff --git a/test/integration/targets/collection/setup.sh b/test/integration/targets/collection/setup.sh new file mode 100755 index 0000000..f1b33a5 --- /dev/null +++ b/test/integration/targets/collection/setup.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Source this file from collection integration tests. +# +# It simplifies several aspects of collection testing: +# +# 1) Collection tests must be executed outside of the ansible source tree. +# Otherwise ansible-test will test the ansible source instead of the test collection. +# The temporary directory provided by ansible-test resides within the ansible source tree. +# +# 2) Sanity test ignore files for collections must be versioned based on the ansible-core version being used. +# This script generates an ignore file with the correct filename for the current ansible-core version. +# +# 3) Sanity tests which are multi-version require an ignore entry per Python version. +# This script replicates these ignore entries for each supported Python version based on the ignored path. + +set -eu -o pipefail + +export TEST_DIR +export WORK_DIR + +TEST_DIR="$PWD" +WORK_DIR="$(mktemp -d)" + +trap 'rm -rf "${WORK_DIR}"' EXIT + +cp -a "${TEST_DIR}/ansible_collections" "${WORK_DIR}" +cd "${WORK_DIR}/ansible_collections/ns/col" + +"${TEST_DIR}/../collection/update-ignore.py" diff --git a/test/integration/targets/collection/update-ignore.py b/test/integration/targets/collection/update-ignore.py new file mode 100755 index 0000000..92a702c --- /dev/null +++ b/test/integration/targets/collection/update-ignore.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +"""Rewrite a sanity ignore file to expand Python versions for import ignores and write the file out with the correct Ansible version in the name.""" + +import os +import sys + +from ansible import release + + +def main(): + ansible_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(release.__file__)))) + source_root = os.path.join(ansible_root, 'test', 'lib') + + sys.path.insert(0, source_root) + + from ansible_test._internal import constants + + src_path = 'tests/sanity/ignore.txt' + + if not os.path.exists(src_path): + print(f'Skipping updates on non-existent ignore file: {src_path}') + return + + directory = os.path.dirname(src_path) + name, ext = os.path.splitext(os.path.basename(src_path)) + major_minor = '.'.join(release.__version__.split('.')[:2]) + dst_path = os.path.join(directory, f'{name}-{major_minor}{ext}') + + with open(src_path) as src_file: + src_lines = src_file.read().splitlines() + + dst_lines = [] + + for line in src_lines: + path, rule = line.split(' ') + + if rule != 'import': + dst_lines.append(line) + continue + + if path.startswith('plugins/module'): + python_versions = constants.SUPPORTED_PYTHON_VERSIONS + else: + python_versions = constants.CONTROLLER_PYTHON_VERSIONS + + for python_version in python_versions: + dst_lines.append(f'{line}-{python_version}') + + ignores = '\n'.join(dst_lines) + '\n' + + with open(dst_path, 'w') as dst_file: + dst_file.write(ignores) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/a.statichost.yml b/test/integration/targets/collections/a.statichost.yml new file mode 100644 index 0000000..683878a --- /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 0000000..996481b --- /dev/null +++ b/test/integration/targets/collections/aliases @@ -0,0 +1,4 @@ +posix +shippable/posix/group1 +shippable/windows/group1 +windows diff --git a/test/integration/targets/collections/ansiballz_dupe/collections/ansible_collections/duplicate/name/plugins/modules/ping.py b/test/integration/targets/collections/ansiballz_dupe/collections/ansible_collections/duplicate/name/plugins/modules/ping.py new file mode 100644 index 0000000..d0fdba7 --- /dev/null +++ b/test/integration/targets/collections/ansiballz_dupe/collections/ansible_collections/duplicate/name/plugins/modules/ping.py @@ -0,0 +1,3 @@ +#!/usr/bin/python +from ansible.module_utils.basic import AnsibleModule +AnsibleModule({}).exit_json(ping='duplicate.name.pong') diff --git a/test/integration/targets/collections/ansiballz_dupe/test_ansiballz_cache_dupe_shortname.yml b/test/integration/targets/collections/ansiballz_dupe/test_ansiballz_cache_dupe_shortname.yml new file mode 100644 index 0000000..2552624 --- /dev/null +++ b/test/integration/targets/collections/ansiballz_dupe/test_ansiballz_cache_dupe_shortname.yml @@ -0,0 +1,15 @@ +- hosts: localhost + gather_facts: false + tasks: + - ping: + register: result1 + + - ping: + collections: + - duplicate.name + register: result2 + + - assert: + that: + - result1.ping == 'pong' + - result2.ping == 'duplicate.name.pong' diff --git a/test/integration/targets/collections/cache.statichost.yml b/test/integration/targets/collections/cache.statichost.yml new file mode 100644 index 0000000..b2adcfa --- /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 0000000..ab33081 --- /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 0000000..cba3812 --- /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 0000000..e3db81b --- /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 0000000..cba3812 --- /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 0000000..21fe324 --- /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 0000000..0747670 --- /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 0000000..5ea354e --- /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 0000000..aa5c3ee --- /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 0000000..eb49a16 --- /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 0000000..51fe852 --- /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 0000000..f5b617d --- /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 0000000..1d1aee7 --- /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/play.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/play.yml new file mode 100644 index 0000000..6be246c --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/play.yml @@ -0,0 +1,4 @@ +- hosts: localhost + gather_facts: false + tasks: + - set_fact: play='tldr' 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 0000000..54402d1 --- /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 0000000..3fab7fe --- /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: + - non_coll_role_to_call_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 0000000..2b1c15f --- /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: non_coll_role_to_call_test_role_output + +- assert: + that: + - 'non_coll_role_to_call_test_role_output.msg == "include another non-coll role"' diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/type/play.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/type/play.yml new file mode 100644 index 0000000..dd6d563 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/type/play.yml @@ -0,0 +1,4 @@ +- hosts: localhost + gather_facts: false + tasks: + - set_fact: play_type='in type subdir' diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/type/subtype/play.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/type/subtype/play.yml new file mode 100644 index 0000000..0e33a76 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/type/subtype/play.yml @@ -0,0 +1,4 @@ +- hosts: localhost + gather_facts: false + tasks: + - set_fact: play_type_subtype='in subtype subdir' 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 0000000..5af7334 --- /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 0000000..b15493d --- /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 0000000..3fa41e8 --- /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 0000000..f0eff30 --- /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 0000000..701d7b4 --- /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 0000000..b534df2 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/callback/usercallback.py @@ -0,0 +1,26 @@ +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' + + 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 0000000..fc19a99 --- /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 0000000..4549f2d --- /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 0000000..a5498a4 --- /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 0000000..0ce239e --- /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 0000000..0723922 --- /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 0000000..dd9818c --- /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 0000000..1cf3d28 --- /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 0000000..bda671f --- /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 0000000..68d2bc7 --- /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 0000000..2b7843d --- /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/MyCSMUOptional.cs b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/MyCSMUOptional.cs new file mode 100644 index 0000000..0a3e758 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/MyCSMUOptional.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 MyCSMUOptional, 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 0000000..09da66d --- /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/MyPSMUOptional.psm1 b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/MyPSMUOptional.psm1 new file mode 100644 index 0000000..1e36159 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/module_utils/MyPSMUOptional.psm1 @@ -0,0 +1,16 @@ +#AnsibleRequires -CSharpUtil Ansible.Invalid -Optional +#AnsibleRequires -Powershell Ansible.ModuleUtils.Invalid -Optional +#AnsibleRequires -CSharpUtil ansible_collections.testns.testcoll.plugins.module_utils.invalid -Optional +#AnsibleRequires -CSharpUtil ansible_collections.testns.testcoll.plugins.module_utils.invalid.invalid -Optional +#AnsibleRequires -Powershell ansible_collections.testns.testcoll.plugins.module_utils.invalid -Optional +#AnsibleRequires -Powershell ansible_collections.testns.testcoll.plugins.module_utils.invalid.invalid -Optional + +Function Invoke-FromUserPSMU { + <# + .SYNOPSIS + Test function + #> + return "from optional 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 0000000..0654d18 --- /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 0000000..ad84710 --- /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 0000000..e69de29 --- /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 0000000..e69de29 --- /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 0000000..7740756 --- /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 0000000..9a31568 --- /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 0000000..e69de29 --- /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 0000000..ebeb8ce --- /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 0000000..3c24bc4 --- /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 0000000..1db0ab9 --- /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 0000000..b48a717 --- /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 0000000..d424796 --- /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 0000000..27747da --- /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 0000000..9698ba6 --- /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 0000000..5a70174 --- /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 0000000..2ca079c --- /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 0000000..e2efada --- /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 0000000..46ccb76 --- /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 0000000..4054e36 --- /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 0000000..b169fde --- /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 0000000..28a0772 --- /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 0000000..703f454 --- /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 0000000..295d432 --- /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 0000000..703f454 --- /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 0000000..3794f49 --- /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 0000000..559e3e5 --- /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 0000000..b945eb6 --- /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 0000000..59cb3c5 --- /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 0000000..31ffd17 --- /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 0000000..26fa53c --- /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 0000000..e017c14 --- /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 0000000..df17583 --- /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 0000000..986d515 --- /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 0000000..ce99bfa --- /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 0000000..af00627 --- /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 0000000..cbca7b7 --- /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/modules/win_uses_optional.ps1 b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_uses_optional.ps1 new file mode 100644 index 0000000..c44dcfe --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/win_uses_optional.ps1 @@ -0,0 +1,33 @@ +#!powershell + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Test builtin C# still works with -Optional +#AnsibleRequires -CSharpUtil Ansible.Basic -Optional + +# Test no failure when importing an invalid builtin C# and pwsh util with -Optional +#AnsibleRequires -CSharpUtil Ansible.Invalid -Optional +#AnsibleRequires -PowerShell Ansible.ModuleUtils.Invalid -Optional + +# Test valid module_util still works with -Optional +#AnsibleRequires -CSharpUtil ansible_collections.testns.testcoll.plugins.module_utils.MyCSMUOptional -Optional +#AnsibleRequires -Powershell ansible_collections.testns.testcoll.plugins.module_utils.MyPSMUOptional -Optional + +# Test no failure when importing an invalid collection C# and pwsh util with -Optional +#AnsibleRequires -CSharpUtil ansible_collections.testns.testcoll.plugins.module_utils.invalid -Optional +#AnsibleRequires -CSharpUtil ansible_collections.testns.testcoll.plugins.module_utils.invalid.invalid -Optional +#AnsibleRequires -Powershell ansible_collections.testns.testcoll.plugins.module_utils.invalid -Optional +#AnsibleRequires -Powershell ansible_collections.testns.testcoll.plugins.module_utils.invalid.invalid -Optional + +$spec = @{ + options = @{ + data = @{ type = "str"; default = "called $(Invoke-FromUserPSMU)" } + } + supports_check_mode = $true +} +$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) + +$module.Result.data = $module.Params.data +$module.Result.csharp = [MyCSMU]::HelloWorld() + +$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 0000000..ba610fb --- /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 0000000..183944f --- /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 0000000..98a8f89 --- /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 0000000..da4e776 --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/vars/custom_vars.py @@ -0,0 +1,48 @@ +# 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): + + # Vars plugins in collections are only loaded when they are enabled by the user. + # If a vars plugin sets REQUIRES_ENABLED = False, a warning should occur (assuming it is loaded). + REQUIRES_ENABLED = False + + 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 0000000..f5dcc0f --- /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 0000000..b3a8819 --- /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 0000000..99297f7 --- /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 0000000..186368f --- /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 0000000..64f5242 --- /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 0000000..9218f3d --- /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 0000000..6eadb7c --- /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 0000000..8c22c1c --- /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 0000000..7c05abb --- /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 0000000..8c22c1c --- /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 0000000..31e3af5 --- /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 0000000..bb4bd6d --- /dev/null +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testredirect/meta/runtime.yml @@ -0,0 +1,23 @@ +plugin_routing: + modules: + ping: + redirect: testns.testcoll.ping + filter: + multi_redirect_filter: + redirect: testns.testredirect.redirect_filter1 + deprecation: + warning_text: deprecation1 + redirect_filter1: + redirect: testns.testredirect.redirect_filter2 + deprecation: + warning_text: deprecation2 + redirect_filter2: + redirect: testns.testcoll.testfilter + deprecation: + warning_text: deprecation3 + dead_end: + redirect: testns.testredirect.bad_redirect + recursive_redirect: + redirect: testns.testredirect.recursive_redirect + invalid_redirect: + redirect: contextual_redirect 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 0000000..e9f9731 --- /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 0000000..66bb5a4 --- /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 0000000..00bb993 --- /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 0000000..7605dc4 --- /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 0000000..ae6941f --- /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 0000000..e69de29 --- /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 0000000..e69de29 --- /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 0000000..eeffe01 --- /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 0000000..0fa98eb --- /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 0000000..0cd9a1d --- /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 0000000..b5792d8 --- /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 0000000..fc14016 --- /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/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 0000000..600b1fd --- /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/import_collection_pb.yml b/test/integration/targets/collections/import_collection_pb.yml new file mode 100644 index 0000000..511d948 --- /dev/null +++ b/test/integration/targets/collections/import_collection_pb.yml @@ -0,0 +1,17 @@ +- import_playbook: testns.testcoll.default_collection_playbook.yml +- import_playbook: testns.testcoll.default_collection_playbook + +# test subdirs +- import_playbook: "testns.testcoll.play" +- import_playbook: "testns.testcoll.type.play" +- import_playbook: "testns.testcoll.type.subtype.play" + +- hosts: localhost + gather_facts: false + tasks: + - name: check values from imports + assert: + that: + - play is defined + - play_type is defined + - play_type_subtype is defined diff --git a/test/integration/targets/collections/includeme.yml b/test/integration/targets/collections/includeme.yml new file mode 100644 index 0000000..219ee58 --- /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 0000000..b508927 --- /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 0000000..c80e1ed --- /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 0000000..7a416a6 --- /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 0000000..81c6e47 --- /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 0000000..903fb4f --- /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 0000000..8cfab46 --- /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-core 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 0000000..b4dd23d --- /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 0000000..cbf6b8e --- /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 0000000..5f11abe --- /dev/null +++ b/test/integration/targets/collections/runme.sh @@ -0,0 +1,150 @@ +#!/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 +unset ANSIBLE_COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH + +# ensure we can call collection module +ansible localhost -m testns.testcoll.testmodule + +# ensure we can call collection module with ansible_collections in path +ANSIBLE_COLLECTIONS_PATH=$PWD/collection_root_sys/ansible_collections ansible localhost -m testns.testcoll.testmodule + + +echo "--- validating callbacks" +# validate FQ callbacks in ansible-playbook +ANSIBLE_CALLBACKS_ENABLED=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_CALLBACKS_ENABLED=formerly_core_callback ansible localhost -m debug 2>&1 | grep -- "usercallback says ok" +## validate missing redirected callback +ANSIBLE_CALLBACKS_ENABLED=formerly_core_missing_callback ansible localhost -m debug 2>&1 | grep -- "Skipping callback plugin 'formerly_core_missing_callback'" +## validate redirected + removed callback (fatal) +ANSIBLE_CALLBACKS_ENABLED=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_CALLBACKS_ENABLED=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_CALLBACKS_ENABLED=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_CALLBACKS_ENABLED=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 + +# test redirects and warnings for filter redirects +echo "testing redirect and deprecation display" +ANSIBLE_DEPRECATION_WARNINGS=yes ansible localhost -m debug -a msg='{{ "data" | testns.testredirect.multi_redirect_filter }}' -vvvvv 2>&1 | tee out.txt +cat out.txt + +test "$(grep out.txt -ce 'deprecation1' -ce 'deprecation2' -ce 'deprecation3')" == 3 +grep out.txt -e 'redirecting (type: filter) testns.testredirect.multi_redirect_filter to testns.testredirect.redirect_filter1' +grep out.txt -e 'redirecting (type: filter) testns.testredirect.redirect_filter1 to testns.testredirect.redirect_filter2' +grep out.txt -e 'redirecting (type: filter) testns.testredirect.redirect_filter2 to testns.testcoll.testfilter' + +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 "$@" + +if [[ ${INVENTORY_PATH} != *.winrm ]]; then + # base invocation tests + ansible-playbook -i "${INVENTORY_PATH}" -v invocation_tests.yml "$@" + + # run playbook from collection, test default again, but with FQCN + ansible-playbook -i "${INVENTORY_PATH}" testns.testcoll.default_collection_playbook.yml "$@" + + # run playbook from collection, test default again, but with FQCN and no extension + ansible-playbook -i "${INVENTORY_PATH}" testns.testcoll.default_collection_playbook "$@" + + # run playbook that imports from collection + ansible-playbook -i "${INVENTORY_PATH}" import_collection_pb.yml "$@" +fi + +# 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 ansiballz cache dupe +ansible-playbook ansiballz_dupe/test_ansiballz_cache_dupe_shortname.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 + +./test_task_resolved_plugin.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 0000000..71f48d5 --- /dev/null +++ b/test/integration/targets/collections/test_bypass_host_loop.yml @@ -0,0 +1,19 @@ +- name: Test collection lookup bypass host list + hosts: all + connection: local + gather_facts: false + collections: + - testns.testcoll + tasks: + - 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 0000000..e4c4d30 --- /dev/null +++ b/test/integration/targets/collections/test_collection_meta.yml @@ -0,0 +1,75 @@ +- 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 + # redirect filter (multiple levels) + - assert: + that: ('data' | testns.testredirect.multi_redirect_filter) == 'data_via_testfilter_from_userdir' + # invalid filter redirect + - debug: msg="{{ 'data' | testns.testredirect.dead_end }}" + ignore_errors: yes + register: redirect_failure + - assert: + that: + - redirect_failure is failed + - "'Could not load \"testns.testredirect.dead_end\"' in redirect_failure.msg" + # recursive filter redirect + - debug: msg="{{ 'data' | testns.testredirect.recursive_redirect }}" + ignore_errors: yes + register: redirect_failure + - assert: + that: + - redirect_failure is failed + - '"recursive collection redirect found for ''testns.testredirect.recursive_redirect''" in redirect_failure.msg' + # invalid filter redirect + - debug: msg="{{ 'data' | testns.testredirect.invalid_redirect }}" + ignore_errors: yes + register: redirect_failure + - assert: + that: + - redirect_failure is failed + - error in redirect_failure.msg + vars: + error: "Collection testns.testredirect contains invalid redirect for testns.testredirect.invalid_redirect: contextual_redirect" + # 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 0000000..11c7f7a --- /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 0000000..8a24b96 --- /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/test_task_resolved_plugin.sh b/test/integration/targets/collections/test_task_resolved_plugin.sh new file mode 100755 index 0000000..444b4f1 --- /dev/null +++ b/test/integration/targets/collections/test_task_resolved_plugin.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +set -eux + +export ANSIBLE_CALLBACKS_ENABLED=display_resolved_action + +ansible-playbook test_task_resolved_plugin/unqualified.yml "$@" | tee out.txt +action_resolution=( + "legacy_action == legacy_action" + "legacy_module == legacy_module" + "debug == ansible.builtin.debug" + "ping == ansible.builtin.ping" +) +for result in "${action_resolution[@]}"; do + grep -q out.txt -e "$result" +done + +ansible-playbook test_task_resolved_plugin/unqualified_and_collections_kw.yml "$@" | tee out.txt +action_resolution=( + "legacy_action == legacy_action" + "legacy_module == legacy_module" + "debug == ansible.builtin.debug" + "ping == ansible.builtin.ping" + "collection_action == test_ns.test_coll.collection_action" + "collection_module == test_ns.test_coll.collection_module" + "formerly_action == test_ns.test_coll.collection_action" + "formerly_module == test_ns.test_coll.collection_module" +) +for result in "${action_resolution[@]}"; do + grep -q out.txt -e "$result" +done + +ansible-playbook test_task_resolved_plugin/fqcn.yml "$@" | tee out.txt +action_resolution=( + "ansible.legacy.legacy_action == legacy_action" + "ansible.legacy.legacy_module == legacy_module" + "ansible.legacy.debug == ansible.builtin.debug" + "ansible.legacy.ping == ansible.builtin.ping" + "ansible.builtin.debug == ansible.builtin.debug" + "ansible.builtin.ping == ansible.builtin.ping" + "test_ns.test_coll.collection_action == test_ns.test_coll.collection_action" + "test_ns.test_coll.collection_module == test_ns.test_coll.collection_module" + "test_ns.test_coll.formerly_action == test_ns.test_coll.collection_action" + "test_ns.test_coll.formerly_module == test_ns.test_coll.collection_module" +) +for result in "${action_resolution[@]}"; do + grep -q out.txt -e "$result" +done diff --git a/test/integration/targets/collections/test_task_resolved_plugin/action_plugins/legacy_action.py b/test/integration/targets/collections/test_task_resolved_plugin/action_plugins/legacy_action.py new file mode 100644 index 0000000..fa4d514 --- /dev/null +++ b/test/integration/targets/collections/test_task_resolved_plugin/action_plugins/legacy_action.py @@ -0,0 +1,14 @@ +# 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): + TRANSFERS_FILES = False + _VALID_ARGS = frozenset() + + def run(self, tmp=None, task_vars=None): + return {'changed': False} diff --git a/test/integration/targets/collections/test_task_resolved_plugin/callback_plugins/display_resolved_action.py b/test/integration/targets/collections/test_task_resolved_plugin/callback_plugins/display_resolved_action.py new file mode 100644 index 0000000..23cce10 --- /dev/null +++ b/test/integration/targets/collections/test_task_resolved_plugin/callback_plugins/display_resolved_action.py @@ -0,0 +1,37 @@ +# (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 + +DOCUMENTATION = ''' + name: display_resolved_action + type: aggregate + short_description: Displays the requested and resolved actions at the end of a playbook. + description: + - Displays the requested and resolved actions in the format "requested == resolved". + requirements: + - Enable in configuration. +''' + +from ansible import constants as C +from ansible.plugins.callback import CallbackBase + + +class CallbackModule(CallbackBase): + + CALLBACK_VERSION = 2.0 + CALLBACK_TYPE = 'aggregate' + CALLBACK_NAME = 'display_resolved_action' + CALLBACK_NEEDS_ENABLED = True + + def __init__(self, *args, **kwargs): + super(CallbackModule, self).__init__(*args, **kwargs) + self.requested_to_resolved = {} + + def v2_playbook_on_task_start(self, task, is_conditional): + self.requested_to_resolved[task.action] = task.resolved_action + + def v2_playbook_on_stats(self, stats): + for requested, resolved in self.requested_to_resolved.items(): + self._display.display("%s == %s" % (requested, resolved), screen_only=True) diff --git a/test/integration/targets/collections/test_task_resolved_plugin/collections/ansible_collections/test_ns/test_coll/meta/runtime.yml b/test/integration/targets/collections/test_task_resolved_plugin/collections/ansible_collections/test_ns/test_coll/meta/runtime.yml new file mode 100644 index 0000000..8c27dba --- /dev/null +++ b/test/integration/targets/collections/test_task_resolved_plugin/collections/ansible_collections/test_ns/test_coll/meta/runtime.yml @@ -0,0 +1,7 @@ +plugin_routing: + modules: + formerly_module: + redirect: test_ns.test_coll.collection_module + action: + formerly_action: + redirect: test_ns.test_coll.collection_action diff --git a/test/integration/targets/collections/test_task_resolved_plugin/collections/ansible_collections/test_ns/test_coll/plugins/action/collection_action.py b/test/integration/targets/collections/test_task_resolved_plugin/collections/ansible_collections/test_ns/test_coll/plugins/action/collection_action.py new file mode 100644 index 0000000..fa4d514 --- /dev/null +++ b/test/integration/targets/collections/test_task_resolved_plugin/collections/ansible_collections/test_ns/test_coll/plugins/action/collection_action.py @@ -0,0 +1,14 @@ +# 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): + TRANSFERS_FILES = False + _VALID_ARGS = frozenset() + + def run(self, tmp=None, task_vars=None): + return {'changed': False} diff --git a/test/integration/targets/collections/test_task_resolved_plugin/collections/ansible_collections/test_ns/test_coll/plugins/modules/collection_module.py b/test/integration/targets/collections/test_task_resolved_plugin/collections/ansible_collections/test_ns/test_coll/plugins/modules/collection_module.py new file mode 100644 index 0000000..8f31226 --- /dev/null +++ b/test/integration/targets/collections/test_task_resolved_plugin/collections/ansible_collections/test_ns/test_coll/plugins/modules/collection_module.py @@ -0,0 +1,29 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = ''' +--- +module: collection_module +short_description: A module to test a task's resolved action name. +description: A module to test a task's resolved action name. +options: {} +author: Ansible Core Team +notes: + - Supports C(check_mode). +''' + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + module = AnsibleModule(supports_check_mode=True, argument_spec={}) + module.exit_json(changed=False) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/test_task_resolved_plugin/fqcn.yml b/test/integration/targets/collections/test_task_resolved_plugin/fqcn.yml new file mode 100644 index 0000000..ab9e925 --- /dev/null +++ b/test/integration/targets/collections/test_task_resolved_plugin/fqcn.yml @@ -0,0 +1,14 @@ +--- +- hosts: localhost + gather_facts: no + tasks: + - ansible.legacy.legacy_action: + - ansible.legacy.legacy_module: + - ansible.legacy.debug: + - ansible.legacy.ping: + - ansible.builtin.debug: + - ansible.builtin.ping: + - test_ns.test_coll.collection_action: + - test_ns.test_coll.collection_module: + - test_ns.test_coll.formerly_action: + - test_ns.test_coll.formerly_module: diff --git a/test/integration/targets/collections/test_task_resolved_plugin/library/legacy_module.py b/test/integration/targets/collections/test_task_resolved_plugin/library/legacy_module.py new file mode 100644 index 0000000..4fd7587 --- /dev/null +++ b/test/integration/targets/collections/test_task_resolved_plugin/library/legacy_module.py @@ -0,0 +1,29 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = ''' +--- +module: legacy_module +short_description: A module to test a task's resolved action name. +description: A module to test a task's resolved action name. +options: {} +author: Ansible Core Team +notes: + - Supports C(check_mode). +''' + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + module = AnsibleModule(supports_check_mode=True, argument_spec={}) + module.exit_json(changed=False) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections/test_task_resolved_plugin/unqualified.yml b/test/integration/targets/collections/test_task_resolved_plugin/unqualified.yml new file mode 100644 index 0000000..076b8cc --- /dev/null +++ b/test/integration/targets/collections/test_task_resolved_plugin/unqualified.yml @@ -0,0 +1,8 @@ +--- +- hosts: localhost + gather_facts: no + tasks: + - legacy_action: + - legacy_module: + - debug: + - ping: diff --git a/test/integration/targets/collections/test_task_resolved_plugin/unqualified_and_collections_kw.yml b/test/integration/targets/collections/test_task_resolved_plugin/unqualified_and_collections_kw.yml new file mode 100644 index 0000000..5af4eda --- /dev/null +++ b/test/integration/targets/collections/test_task_resolved_plugin/unqualified_and_collections_kw.yml @@ -0,0 +1,14 @@ +--- +- hosts: localhost + gather_facts: no + collections: + - test_ns.test_coll + tasks: + - legacy_action: + - legacy_module: + - debug: + - ping: + - collection_action: + - collection_module: + - formerly_action: + - formerly_module: diff --git a/test/integration/targets/collections/testcoll2/MANIFEST.json b/test/integration/targets/collections/testcoll2/MANIFEST.json new file mode 100644 index 0000000..e69de29 --- /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 0000000..7f6eb02 --- /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 0000000..b808897 --- /dev/null +++ b/test/integration/targets/collections/vars_plugin_tests.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash + +set -eux + +# Collections vars plugins must be enabled using the FQCN in the 'enabled' list, 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=./ 2>&1 | tee out.txt + +grep '"collection": "adjacent"' out.txt +grep '"adj_var": "value"' out.txt +grep -v "REQUIRES_ENABLED is not supported" 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=./ 2>&1 | tee out.txt + +grep '"collection": "collection_root_user"' out.txt +grep -v '"adj_var": "value"' out.txt +grep "REQUIRES_ENABLED is not supported" 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 enabling 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 + +# Test plugins in plugin paths that opt-in to require enabling +unset ANSIBLE_VARS_ENABLED +unset ANSIBLE_COLLECTIONS_PATH + + +# 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 '"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 '"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 0000000..cf98ca1 --- /dev/null +++ b/test/integration/targets/collections/windows.yml @@ -0,0 +1,34 @@ +- 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 + + - testns.testcoll.win_uses_optional: + register: uses_coll_optional + + - 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 + # win_uses_optional + - uses_coll_optional.data == "called from optional user_mu" + - uses_coll_optional.csharp == "Hello from user_mu collection-hosted MyCSMUOptional, also Hello from nested user-collection-hosted AnotherCSMU and Hello from subpkg.subcs" diff --git a/test/integration/targets/collections_plugin_namespace/aliases b/test/integration/targets/collections_plugin_namespace/aliases new file mode 100644 index 0000000..8278ec8 --- /dev/null +++ b/test/integration/targets/collections_plugin_namespace/aliases @@ -0,0 +1,2 @@ +shippable/posix/group3 +context/controller diff --git a/test/integration/targets/collections_plugin_namespace/collection_root/ansible_collections/my_ns/my_col/plugins/filter/test_filter.py b/test/integration/targets/collections_plugin_namespace/collection_root/ansible_collections/my_ns/my_col/plugins/filter/test_filter.py new file mode 100644 index 0000000..dca094b --- /dev/null +++ b/test/integration/targets/collections_plugin_namespace/collection_root/ansible_collections/my_ns/my_col/plugins/filter/test_filter.py @@ -0,0 +1,15 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def filter_name(a): + return __name__ + + +class FilterModule(object): + def filters(self): + filters = { + 'filter_name': filter_name, + } + + return filters diff --git a/test/integration/targets/collections_plugin_namespace/collection_root/ansible_collections/my_ns/my_col/plugins/lookup/lookup_name.py b/test/integration/targets/collections_plugin_namespace/collection_root/ansible_collections/my_ns/my_col/plugins/lookup/lookup_name.py new file mode 100644 index 0000000..d0af703 --- /dev/null +++ b/test/integration/targets/collections_plugin_namespace/collection_root/ansible_collections/my_ns/my_col/plugins/lookup/lookup_name.py @@ -0,0 +1,9 @@ +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 [__name__] diff --git a/test/integration/targets/collections_plugin_namespace/collection_root/ansible_collections/my_ns/my_col/plugins/lookup/lookup_no_future_boilerplate.py b/test/integration/targets/collections_plugin_namespace/collection_root/ansible_collections/my_ns/my_col/plugins/lookup/lookup_no_future_boilerplate.py new file mode 100644 index 0000000..79e80f6 --- /dev/null +++ b/test/integration/targets/collections_plugin_namespace/collection_root/ansible_collections/my_ns/my_col/plugins/lookup/lookup_no_future_boilerplate.py @@ -0,0 +1,10 @@ +# do not add future boilerplate to this plugin +# specifically, do not add absolute_import, as the purpose of this plugin is to test implicit relative imports on Python 2.x +__metaclass__ = type + +from ansible.plugins.lookup import LookupBase + + +class LookupModule(LookupBase): + def run(self, terms, variables, **kwargs): + return [__name__] diff --git a/test/integration/targets/collections_plugin_namespace/collection_root/ansible_collections/my_ns/my_col/plugins/test/test_test.py b/test/integration/targets/collections_plugin_namespace/collection_root/ansible_collections/my_ns/my_col/plugins/test/test_test.py new file mode 100644 index 0000000..1739072 --- /dev/null +++ b/test/integration/targets/collections_plugin_namespace/collection_root/ansible_collections/my_ns/my_col/plugins/test/test_test.py @@ -0,0 +1,13 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +def test_name_ok(value): + return __name__ == 'ansible_collections.my_ns.my_col.plugins.test.test_test' + + +class TestModule: + def tests(self): + return { + 'test_name_ok': test_name_ok, + } diff --git a/test/integration/targets/collections_plugin_namespace/collection_root/ansible_collections/my_ns/my_col/roles/test/tasks/main.yml b/test/integration/targets/collections_plugin_namespace/collection_root/ansible_collections/my_ns/my_col/roles/test/tasks/main.yml new file mode 100644 index 0000000..d80f547 --- /dev/null +++ b/test/integration/targets/collections_plugin_namespace/collection_root/ansible_collections/my_ns/my_col/roles/test/tasks/main.yml @@ -0,0 +1,12 @@ +- set_fact: + filter_name: "{{ 1 | my_ns.my_col.filter_name }}" + lookup_name: "{{ lookup('my_ns.my_col.lookup_name') }}" + lookup_no_future_boilerplate: "{{ lookup('my_ns.my_col.lookup_no_future_boilerplate') }}" + test_name_ok: "{{ 1 is my_ns.my_col.test_name_ok }}" + +- assert: + that: + - filter_name == 'ansible_collections.my_ns.my_col.plugins.filter.test_filter' + - lookup_name == 'ansible_collections.my_ns.my_col.plugins.lookup.lookup_name' + - lookup_no_future_boilerplate == 'ansible_collections.my_ns.my_col.plugins.lookup.lookup_no_future_boilerplate' + - test_name_ok diff --git a/test/integration/targets/collections_plugin_namespace/runme.sh b/test/integration/targets/collections_plugin_namespace/runme.sh new file mode 100755 index 0000000..96e83d3 --- /dev/null +++ b/test/integration/targets/collections_plugin_namespace/runme.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -eux + +ANSIBLE_COLLECTIONS_PATH="${PWD}/collection_root" ansible-playbook test.yml -i ../../inventory "$@" diff --git a/test/integration/targets/collections_plugin_namespace/test.yml b/test/integration/targets/collections_plugin_namespace/test.yml new file mode 100644 index 0000000..d1c3f1b --- /dev/null +++ b/test/integration/targets/collections_plugin_namespace/test.yml @@ -0,0 +1,3 @@ +- hosts: testhost + roles: + - my_ns.my_col.test diff --git a/test/integration/targets/collections_relative_imports/aliases b/test/integration/targets/collections_relative_imports/aliases new file mode 100644 index 0000000..996481b --- /dev/null +++ b/test/integration/targets/collections_relative_imports/aliases @@ -0,0 +1,4 @@ +posix +shippable/posix/group1 +shippable/windows/group1 +windows diff --git a/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/PSRel1.psm1 b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/PSRel1.psm1 new file mode 100644 index 0000000..bf81264 --- /dev/null +++ b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/PSRel1.psm1 @@ -0,0 +1,11 @@ +#AnsibleRequires -PowerShell .sub_pkg.PSRel2 + +Function Invoke-FromPSRel1 { + <# + .SYNOPSIS + Test function + #> + return "$(Invoke-FromPSRel2) -> Invoke-FromPSRel1" +} + +Export-ModuleMember -Function Invoke-FromPSRel1 diff --git a/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/PSRel4.psm1 b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/PSRel4.psm1 new file mode 100644 index 0000000..bcb5ec1 --- /dev/null +++ b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/PSRel4.psm1 @@ -0,0 +1,12 @@ +#AnsibleRequires -CSharpUtil .sub_pkg.CSRel5 -Optional +#AnsibleRequires -PowerShell .sub_pkg.PSRelInvalid -Optional + +Function Invoke-FromPSRel4 { + <# + .SYNOPSIS + Test function + #> + return "Invoke-FromPSRel4" +} + +Export-ModuleMember -Function Invoke-FromPSRel4 diff --git a/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/my_util1.py b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/my_util1.py new file mode 100644 index 0000000..196b4ab --- /dev/null +++ b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/my_util1.py @@ -0,0 +1,6 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +def one(): + return 1 diff --git a/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/my_util2.py b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/my_util2.py new file mode 100644 index 0000000..0d985bf --- /dev/null +++ b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/my_util2.py @@ -0,0 +1,8 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from .my_util1 import one + + +def two(): + return one() * 2 diff --git a/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/my_util3.py b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/my_util3.py new file mode 100644 index 0000000..1529d7b --- /dev/null +++ b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/my_util3.py @@ -0,0 +1,8 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from . import my_util2 + + +def three(): + return my_util2.two() + 1 diff --git a/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/sub_pkg/PSRel2.psm1 b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/sub_pkg/PSRel2.psm1 new file mode 100644 index 0000000..d0aa368 --- /dev/null +++ b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/sub_pkg/PSRel2.psm1 @@ -0,0 +1,11 @@ +#AnsibleRequires -PowerShell ansible_collections.my_ns.my_col2.plugins.module_utils.PSRel3 + +Function Invoke-FromPSRel2 { + <# + .SYNOPSIS + Test function + #> + return "$(Invoke-FromPSRel3) -> Invoke-FromPSRel2" +} + +Export-ModuleMember -Function Invoke-FromPSRel2 diff --git a/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/modules/my_module.py b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/modules/my_module.py new file mode 100644 index 0000000..0cdf500 --- /dev/null +++ b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/modules/my_module.py @@ -0,0 +1,24 @@ +#!/usr/bin/python +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.my_util2 import two +from ..module_utils import my_util3 + + +def main(): + module = AnsibleModule( + argument_spec=dict(), + supports_check_mode=True + ) + + result = dict( + two=two(), + three=my_util3.three(), + ) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/modules/win_relative.ps1 b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/modules/win_relative.ps1 new file mode 100644 index 0000000..383df0a --- /dev/null +++ b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/modules/win_relative.ps1 @@ -0,0 +1,10 @@ +#!powershell + +#AnsibleRequires -CSharpUtil Ansible.Basic +#AnsibleRequires -PowerShell ..module_utils.PSRel1 + +$module = [Ansible.Basic.AnsibleModule]::Create($args, @{}) + +$module.Result.data = Invoke-FromPSRel1 + +$module.ExitJson() diff --git a/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/modules/win_relative_optional.ps1 b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/modules/win_relative_optional.ps1 new file mode 100644 index 0000000..9086ca4 --- /dev/null +++ b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/modules/win_relative_optional.ps1 @@ -0,0 +1,17 @@ +#!powershell + +#AnsibleRequires -CSharpUtil Ansible.Basic -Optional +#AnsibleRequires -PowerShell ..module_utils.PSRel4 -optional + +# These do not exist +#AnsibleRequires -CSharpUtil ..invalid_package.name -Optional +#AnsibleRequires -CSharpUtil ..module_utils.InvalidName -optional +#AnsibleRequires -PowerShell ..invalid_package.pwsh_name -optional +#AnsibleRequires -PowerShell ..module_utils.InvalidPwshName -Optional + + +$module = [Ansible.Basic.AnsibleModule]::Create($args, @{}) + +$module.Result.data = Invoke-FromPSRel4 + +$module.ExitJson() diff --git a/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/roles/test/tasks/main.yml b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/roles/test/tasks/main.yml new file mode 100644 index 0000000..9ba0f7e --- /dev/null +++ b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/roles/test/tasks/main.yml @@ -0,0 +1,4 @@ +- name: fully qualified module usage with relative imports + my_ns.my_col.my_module: +- name: collection relative module usage with relative imports + my_module: diff --git a/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col2/plugins/module_utils/PSRel3.psm1 b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col2/plugins/module_utils/PSRel3.psm1 new file mode 100644 index 0000000..46edd5a --- /dev/null +++ b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col2/plugins/module_utils/PSRel3.psm1 @@ -0,0 +1,11 @@ +#AnsibleRequires -CSharpUtil .sub_pkg.CSRel4 + +Function Invoke-FromPSRel3 { + <# + .SYNOPSIS + Test function + #> + return "$([CSRel4]::Invoke()) -> Invoke-FromPSRel3" +} + +Export-ModuleMember -Function Invoke-FromPSRel3 diff --git a/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col2/plugins/module_utils/sub_pkg/CSRel4.cs b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col2/plugins/module_utils/sub_pkg/CSRel4.cs new file mode 100644 index 0000000..c50024b --- /dev/null +++ b/test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col2/plugins/module_utils/sub_pkg/CSRel4.cs @@ -0,0 +1,14 @@ +using System; + +//TypeAccelerator -Name CSRel4 -TypeName TestClass + +namespace ansible_collections.my_ns.my_col.plugins.module_utils.sub_pkg.CSRel4 +{ + public class TestClass + { + public static string Invoke() + { + return "CSRel4.Invoke()"; + } + } +} diff --git a/test/integration/targets/collections_relative_imports/runme.sh b/test/integration/targets/collections_relative_imports/runme.sh new file mode 100755 index 0000000..754efaf --- /dev/null +++ b/test/integration/targets/collections_relative_imports/runme.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -eux + +# 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=test.yml + +fi + +ANSIBLE_COLLECTIONS_PATH="${PWD}/collection_root" ansible-playbook "${TEST_PLAYBOOK}" -i "${INVENTORY_PATH}" "$@" diff --git a/test/integration/targets/collections_relative_imports/test.yml b/test/integration/targets/collections_relative_imports/test.yml new file mode 100644 index 0000000..d1c3f1b --- /dev/null +++ b/test/integration/targets/collections_relative_imports/test.yml @@ -0,0 +1,3 @@ +- hosts: testhost + roles: + - my_ns.my_col.test diff --git a/test/integration/targets/collections_relative_imports/windows.yml b/test/integration/targets/collections_relative_imports/windows.yml new file mode 100644 index 0000000..3a3c548 --- /dev/null +++ b/test/integration/targets/collections_relative_imports/windows.yml @@ -0,0 +1,20 @@ +- hosts: windows + gather_facts: no + tasks: + - name: test out relative imports on Windows modules + my_ns.my_col.win_relative: + register: win_relative + + - name: assert relative imports on Windows modules + assert: + that: + - win_relative.data == 'CSRel4.Invoke() -> Invoke-FromPSRel3 -> Invoke-FromPSRel2 -> Invoke-FromPSRel1' + + - name: test out relative imports on Windows modules with optional import + my_ns.my_col.win_relative_optional: + register: win_relative_optional + + - name: assert relative imports on Windows modules with optional import + assert: + that: + - win_relative_optional.data == 'Invoke-FromPSRel4' diff --git a/test/integration/targets/collections_runtime_pythonpath/aliases b/test/integration/targets/collections_runtime_pythonpath/aliases new file mode 100644 index 0000000..8278ec8 --- /dev/null +++ b/test/integration/targets/collections_runtime_pythonpath/aliases @@ -0,0 +1,2 @@ +shippable/posix/group3 +context/controller diff --git a/test/integration/targets/collections_runtime_pythonpath/ansible-collection-python-dist-boo/ansible_collections/python/dist/plugins/modules/boo.py b/test/integration/targets/collections_runtime_pythonpath/ansible-collection-python-dist-boo/ansible_collections/python/dist/plugins/modules/boo.py new file mode 100644 index 0000000..a2313b1 --- /dev/null +++ b/test/integration/targets/collections_runtime_pythonpath/ansible-collection-python-dist-boo/ansible_collections/python/dist/plugins/modules/boo.py @@ -0,0 +1,28 @@ +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""Say hello.""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + module = AnsibleModule( + argument_spec={ + 'name': {'default': 'world'}, + }, + ) + name = module.params['name'] + + module.exit_json( + msg='Greeting {name} completed.'. + format(name=name.title()), + greeting='Hello, {name}!'.format(name=name), + ) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections_runtime_pythonpath/ansible-collection-python-dist-boo/pyproject.toml b/test/integration/targets/collections_runtime_pythonpath/ansible-collection-python-dist-boo/pyproject.toml new file mode 100644 index 0000000..feec734 --- /dev/null +++ b/test/integration/targets/collections_runtime_pythonpath/ansible-collection-python-dist-boo/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ + "setuptools >= 44", + "wheel", +] +build-backend = "setuptools.build_meta" diff --git a/test/integration/targets/collections_runtime_pythonpath/ansible-collection-python-dist-boo/setup.cfg b/test/integration/targets/collections_runtime_pythonpath/ansible-collection-python-dist-boo/setup.cfg new file mode 100644 index 0000000..d25ebb0 --- /dev/null +++ b/test/integration/targets/collections_runtime_pythonpath/ansible-collection-python-dist-boo/setup.cfg @@ -0,0 +1,15 @@ +[metadata] +name = ansible-collections.python.dist +version = 1.0.0rc2.post3.dev4 + +[options] +package_dir = + = . +packages = + ansible_collections + ansible_collections.python + ansible_collections.python.dist + ansible_collections.python.dist.plugins + ansible_collections.python.dist.plugins.modules +zip_safe = True +include_package_data = True diff --git a/test/integration/targets/collections_runtime_pythonpath/ansible-collection-python-dist-foo/ansible_collections/python/dist/plugins/modules/boo.py b/test/integration/targets/collections_runtime_pythonpath/ansible-collection-python-dist-foo/ansible_collections/python/dist/plugins/modules/boo.py new file mode 100644 index 0000000..1ef0333 --- /dev/null +++ b/test/integration/targets/collections_runtime_pythonpath/ansible-collection-python-dist-foo/ansible_collections/python/dist/plugins/modules/boo.py @@ -0,0 +1,28 @@ +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +"""Say hello in Ukrainian.""" + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + module = AnsibleModule( + argument_spec={ + 'name': {'default': 'світ'}, + }, + ) + name = module.params['name'] + + module.exit_json( + msg='Greeting {name} completed.'. + format(name=name.title()), + greeting='Привіт, {name}!'.format(name=name), + ) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/collections_runtime_pythonpath/runme.sh b/test/integration/targets/collections_runtime_pythonpath/runme.sh new file mode 100755 index 0000000..38c6c64 --- /dev/null +++ b/test/integration/targets/collections_runtime_pythonpath/runme.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +set -eux -o pipefail + + +export PIP_DISABLE_PIP_VERSION_CHECK=1 + + +source virtualenv.sh + + +>&2 echo \ + === Test that the module \ + gets picked up if discoverable \ + via PYTHONPATH env var === +PYTHONPATH="${PWD}/ansible-collection-python-dist-boo:$PYTHONPATH" \ +ansible \ + -m python.dist.boo \ + -a 'name=Bob' \ + -c local localhost \ + "$@" | grep -E '"greeting": "Hello, Bob!",' + + +>&2 echo \ + === Test that the module \ + gets picked up if installed \ + into site-packages === +python -m pip install pep517 +( # Build a binary Python dist (a wheel) using PEP517: + cp -r ansible-collection-python-dist-boo "${OUTPUT_DIR}/" + cd "${OUTPUT_DIR}/ansible-collection-python-dist-boo" + python -m pep517.build --binary --out-dir dist . +) +# Install a pre-built dist with pip: +python -m pip install \ + --no-index \ + -f "${OUTPUT_DIR}/ansible-collection-python-dist-boo/dist/" \ + --only-binary=ansible-collections.python.dist \ + ansible-collections.python.dist +python -m pip show ansible-collections.python.dist +ansible \ + -m python.dist.boo \ + -a 'name=Frodo' \ + -c local localhost \ + "$@" | grep -E '"greeting": "Hello, Frodo!",' + + +>&2 echo \ + === Test that ansible_collections \ + root takes precedence over \ + PYTHONPATH/site-packages === +# This is done by injecting a module with the same FQCN +# into another collection root. +ANSIBLE_COLLECTIONS_PATH="${PWD}/ansible-collection-python-dist-foo" \ +PYTHONPATH="${PWD}/ansible-collection-python-dist-boo:$PYTHONPATH" \ +ansible \ + -m python.dist.boo \ + -a 'name=Степан' \ + -c local localhost \ + "$@" | grep -E '"greeting": "Привіт, Степан!",' |