1- hosts: testhost
2  tasks:
3  # basic test of FQ module lookup and that we got the right one (user-dir hosted)
4  - name: exec FQ module in a user-dir testns collection
5    testns.testcoll.testmodule:
6    register: testmodule_out
7
8  # verifies that distributed collection subpackages are visible under a multi-location namespace (testns exists in user and sys locations)
9  - name: exec FQ module in a sys-dir testns collection
10    testns.coll_in_sys.systestmodule:
11    register: systestmodule_out
12
13  # verifies that content-adjacent collections were automatically added to the installed content roots
14  - name: exec FQ module from content-adjacent collection
15    testns.content_adj.contentadjmodule:
16    register: contentadjmodule_out
17
18  # content should only be loaded from the first visible instance of a collection
19  - name: attempt to look up FQ module in a masked collection
20    testns.testcoll.plugin_lookup:
21      type: module
22      name: testns.testcoll.maskedmodule
23    register: maskedmodule_out
24
25  # ensure the ansible ns can have real collections added to it
26  - name: call an external module in the ansible namespace
27    ansible.bullcoll.bullmodule:
28    register: bullmodule_out
29
30  # ensure the ansible ns cannot override ansible.builtin externally
31  - name: call an external module in the ansible.builtin collection (should use the built in module)
32    ansible.builtin.ping:
33    register: builtin_ping_out
34
35  # action in a collection subdir
36  - name: test subdir action FQ
37    testns.testcoll.action_subdir.subdir_ping_action:
38    register: subdir_ping_action_out
39
40  # module in a collection subdir
41  - name: test subdir module FQ
42    testns.testcoll.module_subdir.subdir_ping_module:
43    register: subdir_ping_module_out
44
45  # module with a granular module_utils import (from (this collection).module_utils.leaf import thingtocall)
46  - name: exec module with granular module utils import from this collection
47    testns.testcoll.uses_leaf_mu_granular_import:
48    register: granular_out
49
50  # module with a granular nested module_utils import (from (this collection).module_utils.base import thingtocall,
51  # where base imports secondary from the same collection's module_utils)
52  - name: exec module with nested module utils from this collection
53    testns.testcoll.uses_base_mu_granular_nested_import:
54    register: granular_nested_out
55
56  # module with a flat module_utils import (import (this collection).module_utils.leaf)
57  - name: exec module with flat module_utils import from this collection
58    testns.testcoll.uses_leaf_mu_flat_import:
59    register: flat_out
60
61  # module with a full-module module_utils import using 'from' (from (this collection).module_utils import leaf)
62  - name: exec module with full-module module_utils import using 'from' from this collection
63    testns.testcoll.uses_leaf_mu_module_import_from:
64    register: from_out
65
66  # module with multiple levels of the same nested package name and imported as a function
67  - name: exec module with multiple levels of the same nested package name imported as a function
68    testns.testcoll.uses_nested_same_as_func:
69    register: from_nested_func
70
71  # module with multiple levels of the same nested package name and imported as a module
72  - name: exec module with multiple levels of the same nested package name imported as a module
73    testns.testcoll.uses_nested_same_as_module:
74    register: from_nested_module
75
76  # module using a bunch of collection-level redirected module_utils
77  - name: exec module using a bunch of collection-level redirected module_utils
78    testns.testcoll.uses_collection_redirected_mu:
79    register: from_redirected_mu
80
81  # module with bogus MU
82  - name: exec module with bogus MU
83    testns.testcoll.uses_mu_missing:
84    ignore_errors: true
85    register: from_missing_mu
86
87  # module with redirected MU, redirect collection not found
88  - name: exec module with a missing redirect target collection
89    testns.testcoll.uses_mu_missing_redirect_collection:
90    ignore_errors: true
91    register: from_missing_redir_collection
92
93  # module with redirected MU, redirect module not found
94  - name: exec module with a missing redirect target module
95    testns.testcoll.uses_mu_missing_redirect_module:
96    ignore_errors: true
97    register: from_missing_redir_module
98
99  - assert:
100      that:
101      - testmodule_out.source == 'user'
102      - systestmodule_out.source == 'sys'
103      - contentadjmodule_out.source == 'content_adj'
104      - not maskedmodule_out.plugin_path
105      - bullmodule_out.source == 'user_ansible_bullcoll'
106      - builtin_ping_out.source is not defined
107      - builtin_ping_out.ping == 'pong'
108      - subdir_ping_action_out is not changed
109      - subdir_ping_module_out is not changed
110      - granular_out.mu_result == 'thingtocall in leaf'
111      - granular_nested_out.mu_result == 'thingtocall in base called thingtocall in secondary'
112      - flat_out.mu_result == 'thingtocall in leaf'
113      - from_out.mu_result == 'thingtocall in leaf'
114      - from_out.mu2_result == 'thingtocall in secondary'
115      - from_out.mu3_result == 'thingtocall in subpkg.submod'
116      - from_out.mu4_result == 'thingtocall in subpkg_with_init'
117      - from_out.mu5_result == 'thingtocall in mod_in_subpkg_with_init'
118      - from_out.mu6_result == 'thingtocall in subpkg.submod'
119      - from_nested_func.mu_result == 'hello from nested_same'
120      - from_nested_module.mu_result == 'hello from nested_same'
121      - from_redirected_mu.mu_result == 'hello from ansible_collections.testns.content_adj.plugins.module_utils.sub1.foomodule'
122      - from_redirected_mu.mu_result2 == 'hello from testns.othercoll.formerly_testcoll_pkg.thing'
123      - from_redirected_mu.mu_result3 == 'hello from formerly_testcoll_pkg.submod.thing'
124      - from_missing_mu is failed
125      - "'Could not find imported module support' in from_missing_mu.msg"
126      - from_missing_redir_collection is failed
127      - "'unable to locate collection bogusns.boguscoll' in from_missing_redir_collection.msg"
128      - from_missing_redir_module is failed
129      - "'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"
130
131
132- hosts: testhost
133  tasks:
134  - name: exercise filters/tests/lookups
135    assert:
136      that:
137      - "'data' | testns.testcoll.testfilter == 'data_via_testfilter_from_userdir'"
138      - "'data' | testns.testcoll.testfilter2 == 'data_via_testfilter2_from_userdir'"
139      - "'data' | testns.testcoll.filter_subdir.test_subdir_filter == 'data_via_testfilter_from_subdir'"
140      - "'from_user' is testns.testcoll.testtest"
141      - "'from_user2' is testns.testcoll.testtest2"
142      - "'subdir_from_user' is testns.testcoll.test_subdir.subdir_test"
143      - lookup('testns.testcoll.mylookup') == 'mylookup_from_user_dir'
144      - lookup('testns.testcoll.mylookup2') == 'mylookup2_from_user_dir'
145      - lookup('testns.testcoll.lookup_subdir.my_subdir_lookup') == 'subdir_lookup_from_user_dir'
146
147  - debug:
148      msg: "{{ 'foo'|testns.testbroken.broken }}"
149    register: result
150    ignore_errors: true
151
152  - assert:
153      that:
154        - |
155          'This is a broken filter plugin.' in result.msg
156
157  - debug:
158      msg: "{{ 'foo'|missing.collection.filter }}"
159    register: result
160    ignore_errors: true
161
162  - assert:
163      that:
164        - result is failed
165
166# ensure that the synthetic ansible.builtin collection limits to builtin plugins, that ansible.legacy loads overrides
167# from legacy plugin dirs, and that a same-named plugin loaded from a real collection is not masked by the others
168- hosts: testhost
169  tasks:
170  - name: test unqualified ping from library dir
171    ping:
172    register: unqualified_ping_out
173
174  - name: test legacy-qualified ping from library dir
175    ansible.legacy.ping:
176    register: legacy_ping_out
177
178  - name: test builtin ping
179    ansible.builtin.ping:
180    register: builtin_ping_out
181
182  - name: test collection-based ping
183    testns.testcoll.ping:
184    register: collection_ping_out
185
186  - assert:
187      that:
188      - unqualified_ping_out.source == 'legacy_library_dir'
189      - legacy_ping_out.source == 'legacy_library_dir'
190      - builtin_ping_out.ping == 'pong'
191      - collection_ping_out.source == 'user'
192
193# verify the default value for the collections list is empty
194- hosts: testhost
195  tasks:
196  - name: sample default collections value
197    testns.testcoll.plugin_lookup:
198    register: coll_default_out
199
200  - assert:
201      that:
202      # in original release, collections defaults to empty, which is mostly equivalent to ansible.legacy
203      - not coll_default_out.collection_list
204
205
206# ensure that inheritance/masking works as expected, that the proper default values are injected when missing,
207# and that the order is preserved if one of the magic values is explicitly specified
208- name: verify collections keyword play/block/task inheritance and magic values
209  hosts: testhost
210  collections:
211  - bogus.fromplay
212  tasks:
213  - name: sample play collections value
214    testns.testcoll.plugin_lookup:
215    register: coll_play_out
216
217  - name: collections override block-level
218    collections:
219    - bogus.fromblock
220    block:
221    - name: sample block collections value
222      testns.testcoll.plugin_lookup:
223      register: coll_block_out
224
225    - name: sample task collections value
226      collections:
227      - bogus.fromtask
228      testns.testcoll.plugin_lookup:
229      register: coll_task_out
230
231    - name: sample task with explicit core
232      collections:
233      - ansible.builtin
234      - bogus.fromtaskexplicitcore
235      testns.testcoll.plugin_lookup:
236      register: coll_task_core
237
238    - name: sample task with explicit legacy
239      collections:
240      - ansible.legacy
241      - bogus.fromtaskexplicitlegacy
242      testns.testcoll.plugin_lookup:
243      register: coll_task_legacy
244
245  - assert:
246      that:
247      # ensure that parent value inheritance is masked properly by explicit setting
248      - coll_play_out.collection_list == ['bogus.fromplay', 'ansible.legacy']
249      - coll_block_out.collection_list == ['bogus.fromblock', 'ansible.legacy']
250      - coll_task_out.collection_list == ['bogus.fromtask', 'ansible.legacy']
251      - coll_task_core.collection_list == ['ansible.builtin', 'bogus.fromtaskexplicitcore']
252      - coll_task_legacy.collection_list == ['ansible.legacy', 'bogus.fromtaskexplicitlegacy']
253
254- name: verify unqualified plugin resolution behavior
255  hosts: testhost
256  collections:
257  - testns.testcoll
258  - testns.coll_in_sys
259  - testns.contentadj
260  tasks:
261  # basic test of unqualified module lookup and that we got the right one (user-dir hosted, there's another copy of
262  # this one in the same-named collection in sys dir that should be masked
263  - name: exec unqualified module in a user-dir testns collection
264    testmodule:
265    register: testmodule_out
266
267  # use another collection to verify that we're looking in all collections listed on the play
268  - name: exec unqualified module in a sys-dir testns collection
269    systestmodule:
270    register: systestmodule_out
271
272  - assert:
273      that:
274      - testmodule_out.source == 'user'
275      - systestmodule_out.source == 'sys'
276
277# test keyword-static execution of a FQ collection-backed role with "tasks/main.yaml"
278- name: verify collection-backed role execution (keyword static)
279  hosts: testhost
280  collections:
281  # set to ansible.builtin only to ensure that roles function properly without inheriting the play's collections config
282  - ansible.builtin
283  vars:
284    test_role_input: keyword static
285  roles:
286  - role: testns.testcoll.testrole_main_yaml
287  tasks:
288  - name: ensure role executed
289    assert:
290      that:
291      - test_role_output.msg == test_role_input
292      - testrole_source == 'collection'
293
294
295# test dynamic execution of a FQ collection-backed role
296- name: verify collection-backed role execution (dynamic)
297  hosts: testhost
298  collections:
299  # set to ansible.builtin only to ensure that roles function properly without inheriting the play's collections config
300  - ansible.builtin
301  vars:
302    test_role_input: dynamic
303  tasks:
304  - include_role:
305      name: testns.testcoll.testrole
306  - name: ensure role executed
307    assert:
308      that:
309      - test_role_output.msg == test_role_input
310      - testrole_source == 'collection'
311
312# test task-static execution of a FQ collection-backed role
313- name: verify collection-backed role execution (task static)
314  hosts: testhost
315  collections:
316  - ansible.builtin
317  vars:
318    test_role_input: task static
319  tasks:
320  - import_role:
321      name: testns.testcoll.testrole
322  - name: ensure role executed
323    assert:
324      that:
325      - test_role_output.msg == test_role_input
326      - testrole_source == 'collection'
327
328
329# test a legacy playbook-adjacent role, ensure that play collections config is not inherited
330- name: verify legacy playbook-adjacent role behavior
331  hosts: testhost
332  collections:
333  - bogus.bogus
334  vars:
335    test_role_input: legacy playbook-adjacent
336  roles:
337  - testrole
338# FIXME: this should technically work to look up a playbook-adjacent role
339#  - ansible.legacy.testrole
340  tasks:
341  - name: ensure role executed
342    assert:
343      that:
344      - test_role_output.msg == test_role_input
345      - testrole_source == 'legacy roles dir'
346
347
348# test dynamic execution of a FQ collection-backed role
349- name: verify collection-backed role execution in subdir (include)
350  hosts: testhost
351  vars:
352    test_role_input: dynamic (subdir)
353  tasks:
354  - include_role:
355      name: testns.testcoll.role_subdir.subdir_testrole
356  - name: ensure role executed
357    assert:
358      that:
359      - test_role_output.msg == test_role_input
360      - testrole_source == 'collection'
361
362
363# test collection-relative role deps (keyword static)
364- name: verify collection-relative role deps
365  hosts: testhost
366  vars:
367    outer_role_input: keyword static outer
368    test_role_input: keyword static inner
369  roles:
370    - testns.testcoll.calls_intra_collection_dep_role_unqualified
371  tasks:
372    - assert:
373        that:
374          - outer_role_output.msg == outer_role_input
375          - test_role_output.msg == test_role_input
376          - testrole_source == 'collection'
377
378# test collection-relative role deps (task static)
379- name: verify collection-relative role deps
380  hosts: testhost
381  vars:
382    outer_role_input: task static outer
383    test_role_input: task static inner
384  tasks:
385    - import_role:
386        name: testns.testcoll.calls_intra_collection_dep_role_unqualified
387    - assert:
388        that:
389          - outer_role_output.msg == outer_role_input
390          - test_role_output.msg == test_role_input
391          - testrole_source == 'collection'
392
393# test collection-relative role deps (task dynamic)
394- name: verify collection-relative role deps
395  hosts: testhost
396  vars:
397    outer_role_input: task dynamic outer
398    test_role_input: task dynamic inner
399  tasks:
400    - include_role:
401        name: testns.testcoll.calls_intra_collection_dep_role_unqualified
402    - assert:
403        that:
404          - outer_role_output.msg == outer_role_input
405          - test_role_output.msg == test_role_input
406          - testrole_source == 'collection'
407
408
409- name: validate static task include behavior
410  hosts: testhost
411  collections:
412  - bogus.bogus
413  tasks:
414  - import_tasks: includeme.yml
415
416
417- name: validate dynamic task include behavior
418  hosts: testhost
419  collections:
420  - bogus.bogus
421  tasks:
422  - include_tasks: includeme.yml
423
424
425- import_playbook: test_collection_meta.yml
426- name: Test FQCN handlers
427  hosts: testhost
428  vars:
429    handler_counter: 0
430  roles:
431    - testns.testcoll.test_fqcn_handlers
432
433- name: Ensure a collection role can call a standalone role
434  hosts: testhost
435  roles:
436    - testns.testcoll.call_standalone
437
438# Issue https://github.com/ansible/ansible/issues/69054
439- name: Test collection as string
440  hosts: testhost
441  collections: foo
442  tasks:
443    - debug: msg="Test"
444