ansible-core 2.17.5rc1__py3-none-any.whl → 2.18.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ansible-core might be problematic. Click here for more details.

Files changed (330) hide show
  1. ansible/__main__.py +2 -17
  2. ansible/cli/__init__.py +3 -15
  3. ansible/cli/config.py +187 -24
  4. ansible/cli/console.py +1 -1
  5. ansible/cli/doc.py +38 -16
  6. ansible/cli/galaxy.py +3 -49
  7. ansible/cli/inventory.py +2 -2
  8. ansible/cli/pull.py +2 -2
  9. ansible/cli/scripts/ansible_connection_cli_stub.py +1 -10
  10. ansible/config/base.yml +127 -57
  11. ansible/config/manager.py +89 -11
  12. ansible/constants.py +32 -9
  13. ansible/errors/__init__.py +5 -0
  14. ansible/executor/interpreter_discovery.py +1 -1
  15. ansible/executor/play_iterator.py +34 -0
  16. ansible/executor/playbook_executor.py +1 -4
  17. ansible/executor/powershell/become_wrapper.ps1 +4 -5
  18. ansible/executor/powershell/bootstrap_wrapper.ps1 +2 -3
  19. ansible/executor/powershell/exec_wrapper.ps1 +1 -1
  20. ansible/executor/powershell/module_manifest.py +2 -2
  21. ansible/executor/task_executor.py +50 -39
  22. ansible/executor/task_queue_manager.py +1 -1
  23. ansible/executor/task_result.py +1 -1
  24. ansible/galaxy/api.py +3 -4
  25. ansible/galaxy/collection/__init__.py +21 -10
  26. ansible/galaxy/collection/concrete_artifact_manager.py +10 -5
  27. ansible/galaxy/collection/galaxy_api_proxy.py +10 -16
  28. ansible/galaxy/collection/gpg.py +17 -23
  29. ansible/galaxy/data/COPYING +7 -0
  30. ansible/galaxy/data/apb/Dockerfile.j2 +1 -0
  31. ansible/galaxy/data/apb/Makefile.j2 +1 -0
  32. ansible/galaxy/data/apb/README.md +7 -3
  33. ansible/galaxy/data/apb/apb.yml.j2 +1 -0
  34. ansible/galaxy/data/apb/defaults/main.yml.j2 +1 -0
  35. ansible/galaxy/data/apb/handlers/main.yml.j2 +1 -0
  36. ansible/galaxy/data/apb/meta/main.yml.j2 +1 -0
  37. ansible/galaxy/data/apb/playbooks/deprovision.yml.j2 +1 -0
  38. ansible/galaxy/data/apb/playbooks/provision.yml.j2 +1 -0
  39. ansible/galaxy/data/apb/tasks/main.yml.j2 +1 -0
  40. ansible/galaxy/data/apb/tests/ansible.cfg +1 -0
  41. ansible/galaxy/data/apb/tests/inventory +1 -0
  42. ansible/galaxy/data/apb/tests/test.yml.j2 +1 -0
  43. ansible/galaxy/data/apb/vars/main.yml.j2 +1 -0
  44. ansible/galaxy/data/collections_galaxy_meta.yml +1 -0
  45. ansible/galaxy/data/container/defaults/main.yml.j2 +1 -0
  46. ansible/galaxy/data/container/handlers/main.yml.j2 +1 -0
  47. ansible/galaxy/data/container/meta/container.yml.j2 +1 -0
  48. ansible/galaxy/data/container/meta/main.yml.j2 +1 -0
  49. ansible/galaxy/data/container/tasks/main.yml.j2 +1 -0
  50. ansible/galaxy/data/container/tests/ansible.cfg +1 -0
  51. ansible/galaxy/data/container/tests/inventory +1 -0
  52. ansible/galaxy/data/container/tests/test.yml.j2 +1 -0
  53. ansible/galaxy/data/container/vars/main.yml.j2 +1 -0
  54. ansible/galaxy/data/default/collection/README.md.j2 +1 -0
  55. ansible/galaxy/data/default/collection/galaxy.yml.j2 +1 -0
  56. ansible/galaxy/data/default/collection/meta/runtime.yml +1 -0
  57. ansible/galaxy/data/default/collection/plugins/README.md.j2 +1 -0
  58. ansible/galaxy/data/default/role/defaults/main.yml.j2 +1 -0
  59. ansible/galaxy/data/default/role/handlers/main.yml.j2 +1 -0
  60. ansible/galaxy/data/default/role/meta/main.yml.j2 +1 -0
  61. ansible/galaxy/data/default/role/tasks/main.yml.j2 +1 -0
  62. ansible/galaxy/data/default/role/tests/inventory +1 -0
  63. ansible/galaxy/data/default/role/tests/test.yml.j2 +1 -0
  64. ansible/galaxy/data/default/role/vars/main.yml.j2 +1 -0
  65. ansible/galaxy/data/network/cliconf_plugins/example.py.j2 +1 -0
  66. ansible/galaxy/data/network/defaults/main.yml.j2 +1 -0
  67. ansible/galaxy/data/network/library/example_command.py.j2 +1 -0
  68. ansible/galaxy/data/network/library/example_config.py.j2 +1 -0
  69. ansible/galaxy/data/network/library/example_facts.py.j2 +1 -0
  70. ansible/galaxy/data/network/meta/main.yml.j2 +1 -0
  71. ansible/galaxy/data/network/module_utils/example.py.j2 +1 -0
  72. ansible/galaxy/data/network/netconf_plugins/example.py.j2 +1 -0
  73. ansible/galaxy/data/network/tasks/main.yml.j2 +1 -0
  74. ansible/galaxy/data/network/terminal_plugins/example.py.j2 +1 -0
  75. ansible/galaxy/data/network/tests/inventory +1 -0
  76. ansible/galaxy/data/network/tests/test.yml.j2 +1 -0
  77. ansible/galaxy/data/network/vars/main.yml.j2 +1 -0
  78. ansible/galaxy/dependency_resolution/providers.py +3 -3
  79. ansible/galaxy/role.py +1 -1
  80. ansible/galaxy/token.py +20 -8
  81. ansible/keyword_desc.yml +1 -1
  82. ansible/module_utils/_internal/__init__.py +0 -0
  83. ansible/module_utils/_internal/_concurrent/__init__.py +0 -0
  84. ansible/module_utils/_internal/_concurrent/_daemon_threading.py +28 -0
  85. ansible/module_utils/_internal/_concurrent/_futures.py +21 -0
  86. ansible/module_utils/ansible_release.py +2 -2
  87. ansible/module_utils/api.py +2 -2
  88. ansible/module_utils/basic.py +8 -8
  89. ansible/module_utils/common/collections.py +1 -1
  90. ansible/module_utils/common/file.py +0 -6
  91. ansible/module_utils/common/process.py +22 -9
  92. ansible/module_utils/common/text/converters.py +5 -8
  93. ansible/module_utils/common/text/formatters.py +20 -4
  94. ansible/module_utils/common/validation.py +33 -25
  95. ansible/module_utils/compat/paramiko.py +6 -1
  96. ansible/module_utils/compat/selinux.py +2 -2
  97. ansible/module_utils/connection.py +8 -24
  98. ansible/module_utils/csharp/Ansible.Become.cs +14 -25
  99. ansible/module_utils/csharp/Ansible.Process.cs +1 -1
  100. ansible/module_utils/distro/__init__.py +1 -1
  101. ansible/module_utils/distro/_distro.py +8 -4
  102. ansible/module_utils/facts/collector.py +2 -0
  103. ansible/module_utils/facts/default_collectors.py +3 -1
  104. ansible/module_utils/facts/hardware/aix.py +54 -52
  105. ansible/module_utils/facts/hardware/darwin.py +37 -34
  106. ansible/module_utils/facts/hardware/freebsd.py +55 -15
  107. ansible/module_utils/facts/hardware/hpux.py +3 -0
  108. ansible/module_utils/facts/hardware/linux.py +101 -57
  109. ansible/module_utils/facts/hardware/netbsd.py +3 -0
  110. ansible/module_utils/facts/hardware/openbsd.py +4 -1
  111. ansible/module_utils/facts/hardware/sunos.py +7 -1
  112. ansible/module_utils/facts/network/aix.py +16 -17
  113. ansible/module_utils/facts/network/fc_wwn.py +4 -1
  114. ansible/module_utils/facts/network/hpux.py +21 -4
  115. ansible/module_utils/facts/network/iscsi.py +7 -8
  116. ansible/module_utils/facts/network/linux.py +0 -2
  117. ansible/module_utils/facts/other/facter.py +9 -4
  118. ansible/module_utils/facts/other/ohai.py +5 -5
  119. ansible/module_utils/facts/packages.py +49 -7
  120. ansible/module_utils/facts/sysctl.py +33 -31
  121. ansible/module_utils/facts/system/distribution.py +2 -2
  122. ansible/module_utils/facts/system/local.py +12 -22
  123. ansible/module_utils/facts/system/service_mgr.py +3 -1
  124. ansible/module_utils/facts/system/systemd.py +47 -0
  125. ansible/module_utils/facts/timeout.py +1 -1
  126. ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 +1 -1
  127. ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1 +1 -1
  128. ansible/module_utils/splitter.py +1 -1
  129. ansible/modules/add_host.py +1 -1
  130. ansible/modules/apt.py +43 -32
  131. ansible/modules/apt_key.py +6 -6
  132. ansible/modules/apt_repository.py +23 -14
  133. ansible/modules/assemble.py +7 -2
  134. ansible/modules/assert.py +4 -4
  135. ansible/modules/blockinfile.py +3 -6
  136. ansible/modules/command.py +1 -1
  137. ansible/modules/copy.py +4 -4
  138. ansible/modules/cron.py +13 -10
  139. ansible/modules/deb822_repository.py +16 -17
  140. ansible/modules/debconf.py +25 -22
  141. ansible/modules/debug.py +1 -1
  142. ansible/modules/dnf.py +79 -164
  143. ansible/modules/dnf5.py +54 -29
  144. ansible/modules/dpkg_selections.py +2 -2
  145. ansible/modules/expect.py +2 -2
  146. ansible/modules/fetch.py +2 -2
  147. ansible/modules/file.py +5 -3
  148. ansible/modules/find.py +40 -12
  149. ansible/modules/gather_facts.py +4 -2
  150. ansible/modules/get_url.py +29 -24
  151. ansible/modules/git.py +35 -35
  152. ansible/modules/group.py +71 -1
  153. ansible/modules/hostname.py +2 -4
  154. ansible/modules/include_vars.py +5 -5
  155. ansible/modules/iptables.py +13 -16
  156. ansible/modules/known_hosts.py +16 -13
  157. ansible/modules/lineinfile.py +1 -4
  158. ansible/modules/meta.py +6 -1
  159. ansible/modules/mount_facts.py +651 -0
  160. ansible/modules/package_facts.py +63 -80
  161. ansible/modules/pause.py +4 -3
  162. ansible/modules/pip.py +14 -14
  163. ansible/modules/replace.py +1 -4
  164. ansible/modules/rpm_key.py +31 -11
  165. ansible/modules/service.py +8 -8
  166. ansible/modules/service_facts.py +20 -5
  167. ansible/modules/set_stats.py +1 -1
  168. ansible/modules/setup.py +3 -3
  169. ansible/modules/stat.py +3 -3
  170. ansible/modules/subversion.py +1 -1
  171. ansible/modules/systemd.py +16 -10
  172. ansible/modules/systemd_service.py +16 -10
  173. ansible/modules/sysvinit.py +4 -4
  174. ansible/modules/unarchive.py +35 -22
  175. ansible/modules/uri.py +24 -18
  176. ansible/modules/user.py +148 -13
  177. ansible/modules/validate_argument_spec.py +3 -3
  178. ansible/modules/wait_for_connection.py +2 -1
  179. ansible/modules/yum_repository.py +136 -179
  180. ansible/parsing/dataloader.py +2 -2
  181. ansible/parsing/mod_args.py +11 -10
  182. ansible/parsing/vault/__init__.py +8 -3
  183. ansible/parsing/yaml/constructor.py +10 -8
  184. ansible/parsing/yaml/objects.py +1 -1
  185. ansible/playbook/base.py +12 -23
  186. ansible/playbook/helpers.py +4 -0
  187. ansible/playbook/loop_control.py +8 -0
  188. ansible/playbook/play.py +4 -22
  189. ansible/playbook/play_context.py +0 -16
  190. ansible/playbook/playbook_include.py +2 -2
  191. ansible/playbook/role/__init__.py +2 -2
  192. ansible/plugins/__init__.py +2 -0
  193. ansible/plugins/action/__init__.py +7 -9
  194. ansible/plugins/action/dnf.py +7 -5
  195. ansible/plugins/action/package.py +5 -4
  196. ansible/plugins/action/reboot.py +2 -2
  197. ansible/plugins/become/__init__.py +1 -1
  198. ansible/plugins/callback/__init__.py +44 -3
  199. ansible/plugins/callback/default.py +1 -1
  200. ansible/plugins/cliconf/__init__.py +1 -1
  201. ansible/plugins/connection/paramiko_ssh.py +2 -80
  202. ansible/plugins/connection/psrp.py +33 -82
  203. ansible/plugins/connection/ssh.py +0 -8
  204. ansible/plugins/connection/winrm.py +46 -1
  205. ansible/plugins/doc_fragments/connection_pipelining.py +2 -2
  206. ansible/plugins/doc_fragments/constructed.py +10 -10
  207. ansible/plugins/doc_fragments/default_callback.py +8 -8
  208. ansible/plugins/doc_fragments/files.py +5 -5
  209. ansible/plugins/doc_fragments/inventory_cache.py +2 -2
  210. ansible/plugins/doc_fragments/result_format_callback.py +6 -6
  211. ansible/plugins/doc_fragments/return_common.py +1 -1
  212. ansible/plugins/doc_fragments/shell_common.py +2 -10
  213. ansible/plugins/doc_fragments/shell_windows.py +0 -9
  214. ansible/plugins/doc_fragments/url.py +2 -2
  215. ansible/plugins/doc_fragments/url_windows.py +4 -5
  216. ansible/plugins/doc_fragments/validate.py +1 -1
  217. ansible/plugins/filter/core.py +2 -0
  218. ansible/plugins/filter/human_to_bytes.yml +9 -0
  219. ansible/plugins/filter/password_hash.yml +1 -1
  220. ansible/plugins/filter/strftime.yml +1 -1
  221. ansible/plugins/filter/to_nice_json.yml +7 -3
  222. ansible/plugins/filter/to_uuid.yml +1 -1
  223. ansible/plugins/filter/unique.yml +28 -0
  224. ansible/plugins/inventory/script.py +1 -1
  225. ansible/plugins/list.py +1 -1
  226. ansible/plugins/loader.py +0 -11
  227. ansible/plugins/lookup/config.py +1 -1
  228. ansible/plugins/lookup/csvfile.py +21 -9
  229. ansible/plugins/lookup/env.py +8 -9
  230. ansible/plugins/lookup/ini.py +10 -1
  231. ansible/plugins/lookup/random_choice.py +2 -2
  232. ansible/plugins/lookup/url.py +7 -2
  233. ansible/plugins/shell/__init__.py +15 -20
  234. ansible/plugins/shell/powershell.py +9 -6
  235. ansible/plugins/strategy/__init__.py +18 -7
  236. ansible/plugins/strategy/linear.py +1 -13
  237. ansible/plugins/test/core.py +23 -1
  238. ansible/plugins/test/issubset.yml +1 -1
  239. ansible/plugins/test/subset.yml +1 -1
  240. ansible/plugins/test/timedout.yml +20 -0
  241. ansible/plugins/test/vault_encrypted.yml +6 -6
  242. ansible/plugins/test/vaulted_file.yml +19 -0
  243. ansible/release.py +2 -2
  244. ansible/template/__init__.py +3 -8
  245. ansible/utils/collection_loader/_collection_finder.py +23 -55
  246. ansible/utils/display.py +44 -31
  247. ansible/utils/galaxy.py +1 -1
  248. ansible/utils/jsonrpc.py +1 -1
  249. ansible/utils/listify.py +1 -5
  250. ansible/utils/path.py +3 -0
  251. ansible/utils/vars.py +18 -27
  252. ansible/vars/manager.py +7 -150
  253. ansible/vars/plugins.py +1 -1
  254. ansible_core-2.18.0rc1.dist-info/Apache-License.txt +202 -0
  255. {ansible_core-2.17.5rc1.dist-info → ansible_core-2.18.0rc1.dist-info}/METADATA +36 -23
  256. ansible_core-2.18.0rc1.dist-info/MIT-license.txt +14 -0
  257. ansible_core-2.18.0rc1.dist-info/PSF-license.txt +48 -0
  258. {ansible_core-2.17.5rc1.dist-info → ansible_core-2.18.0rc1.dist-info}/RECORD +321 -316
  259. {ansible_core-2.17.5rc1.dist-info → ansible_core-2.18.0rc1.dist-info}/entry_points.txt +1 -1
  260. ansible_core-2.18.0rc1.dist-info/simplified_bsd.txt +8 -0
  261. ansible_test/_data/completion/docker.txt +7 -7
  262. ansible_test/_data/completion/remote.txt +5 -4
  263. ansible_test/_data/completion/windows.txt +4 -4
  264. ansible_test/_data/requirements/ansible-test.txt +1 -2
  265. ansible_test/_data/requirements/constraints.txt +1 -2
  266. ansible_test/_data/requirements/sanity.ansible-doc.txt +3 -3
  267. ansible_test/_data/requirements/sanity.changelog.in +1 -1
  268. ansible_test/_data/requirements/sanity.changelog.txt +4 -4
  269. ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
  270. ansible_test/_data/requirements/sanity.import.txt +1 -1
  271. ansible_test/_data/requirements/sanity.integration-aliases.txt +1 -1
  272. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  273. ansible_test/_data/requirements/sanity.pylint.txt +6 -8
  274. ansible_test/_data/requirements/sanity.runtime-metadata.txt +2 -2
  275. ansible_test/_data/requirements/sanity.validate-modules.txt +3 -3
  276. ansible_test/_data/requirements/sanity.yamllint.in +1 -0
  277. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  278. ansible_test/_internal/ansible_util.py +8 -35
  279. ansible_test/_internal/ci/azp.py +1 -1
  280. ansible_test/_internal/classification/__init__.py +0 -2
  281. ansible_test/_internal/cli/parsers/key_value_parsers.py +3 -0
  282. ansible_test/_internal/commands/integration/cloud/hcloud.py +1 -1
  283. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  284. ansible_test/_internal/commands/integration/cloud/nios.py +1 -1
  285. ansible_test/_internal/commands/sanity/__init__.py +96 -19
  286. ansible_test/_internal/commands/sanity/pylint.py +20 -24
  287. ansible_test/_internal/completion.py +2 -0
  288. ansible_test/_internal/constants.py +0 -1
  289. ansible_test/_internal/coverage_util.py +1 -2
  290. ansible_test/_internal/docker_util.py +10 -2
  291. ansible_test/_internal/encoding.py +4 -4
  292. ansible_test/_internal/host_configs.py +10 -0
  293. ansible_test/_internal/host_profiles.py +9 -13
  294. ansible_test/_internal/pypi_proxy.py +1 -1
  295. ansible_test/_internal/python_requirements.py +5 -14
  296. ansible_test/_internal/timeout.py +1 -1
  297. ansible_test/_internal/util.py +56 -8
  298. ansible_test/_internal/util_common.py +5 -1
  299. ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.json +3 -1
  300. ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.py +6 -3
  301. ansible_test/_util/controller/sanity/code-smell/empty-init.json +0 -2
  302. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +5 -0
  303. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +5 -0
  304. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +5 -0
  305. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +6 -0
  306. ansible_test/_util/controller/sanity/pylint/config/default.cfg +6 -0
  307. ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +1 -19
  308. ansible_test/_util/controller/sanity/pylint/plugins/hide_unraisable.py +3 -4
  309. ansible_test/_util/controller/sanity/shellcheck/exclude.txt +1 -0
  310. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +67 -2
  311. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +27 -5
  312. ansible_test/_util/target/cli/ansible_test_cli_stub.py +0 -0
  313. ansible_test/_util/target/common/constants.py +2 -2
  314. ansible_test/_util/target/injector/python.py +5 -0
  315. ansible_test/_util/target/pytest/plugins/ansible_pytest_coverage.py +6 -0
  316. ansible_test/_util/target/sanity/import/importer.py +1 -1
  317. ansible_test/_util/target/setup/bootstrap.sh +6 -17
  318. ansible_test/_util/target/setup/requirements.py +18 -24
  319. ansible_test/config/config.yml +1 -1
  320. ansible_core-2.17.5rc1.data/scripts/ansible-test +0 -44
  321. ansible_test/_data/requirements/sanity.mypy.in +0 -10
  322. ansible_test/_data/requirements/sanity.mypy.txt +0 -18
  323. ansible_test/_internal/commands/sanity/mypy.py +0 -274
  324. ansible_test/_util/controller/sanity/mypy/ansible-core.ini +0 -116
  325. ansible_test/_util/controller/sanity/mypy/ansible-test.ini +0 -27
  326. ansible_test/_util/controller/sanity/mypy/modules.ini +0 -92
  327. ansible_test/_util/controller/sanity/mypy/packaging.ini +0 -20
  328. {ansible_core-2.17.5rc1.dist-info → ansible_core-2.18.0rc1.dist-info}/COPYING +0 -0
  329. {ansible_core-2.17.5rc1.dist-info → ansible_core-2.18.0rc1.dist-info}/WHEEL +0 -0
  330. {ansible_core-2.17.5rc1.dist-info → ansible_core-2.18.0rc1.dist-info}/top_level.txt +0 -0
@@ -10,6 +10,13 @@ DOCUMENTATION:
10
10
  description: A list.
11
11
  type: list
12
12
  required: true
13
+ case_sensitive:
14
+ description: Whether to consider case when comparing elements.
15
+ default: false
16
+ type: bool
17
+ attribute:
18
+ description: Filter objects with unique values for this attribute.
19
+ type: str
13
20
  seealso:
14
21
  - plugin_type: filter
15
22
  plugin: ansible.builtin.difference
@@ -24,6 +31,27 @@ EXAMPLES: |
24
31
  # list1: [1, 2, 5, 1, 3, 4, 10]
25
32
  {{ list1 | unique }}
26
33
  # => [1, 2, 5, 3, 4, 10]
34
+
35
+ # return case sensitive unique elements
36
+ {{ ['a', 'A', 'a'] | unique(case_sensitive='true') }}
37
+ # => ['a', 'A']
38
+
39
+ # return case insensitive unique elements
40
+ {{ ['b', 'B', 'b'] | unique() }}
41
+ # => ['b']
42
+
43
+ # return unique elements of list based on attribute
44
+ # => [{"age": 12, "name": "a" }, { "age": 14, "name": "b"}]
45
+ - debug:
46
+ msg: "{{ sample | unique(attribute='age') }}"
47
+ vars:
48
+ sample:
49
+ - name: a
50
+ age: 12
51
+ - name: b
52
+ age: 14
53
+ - name: c
54
+ age: 14
27
55
  RETURN:
28
56
  _value:
29
57
  description: A list with unique elements, also known as a set.
@@ -130,7 +130,7 @@ EXAMPLES = r'''# fmt: code
130
130
  mandatory_options = arg_parser.add_mutually_exclusive_group()
131
131
  mandatory_options.add_argument('--list', action='store', nargs="*", help="Get inventory JSON from our API")
132
132
  mandatory_options.add_argument('--host', action='store',
133
- help="Get variables for specific host, not used but kept for compatability")
133
+ help="Get variables for specific host, not used but kept for compatibility")
134
134
 
135
135
  try:
136
136
  config = load_config()
ansible/plugins/list.py CHANGED
@@ -84,7 +84,7 @@ def _list_plugins_from_paths(ptype, dirs, collection, depth=0):
84
84
  to_native(b_ext) in C.REJECT_EXTS, # general extensions to ignore
85
85
  b_ext in (b'.yml', b'.yaml', b'.json'), # ignore docs files TODO: constant!
86
86
  plugin in IGNORE.get(bkey, ()), # plugin in reject list
87
- os.path.islink(full_path), # skip aliases, author should document in 'aliaes' field
87
+ os.path.islink(full_path), # skip aliases, author should document in 'aliases' field
88
88
  ]):
89
89
  continue
90
90
 
ansible/plugins/loader.py CHANGED
@@ -1110,10 +1110,6 @@ class PluginLoader:
1110
1110
  needs_enabled = False
1111
1111
  if hasattr(obj, 'REQUIRES_ENABLED'):
1112
1112
  needs_enabled = obj.REQUIRES_ENABLED
1113
- elif hasattr(obj, 'REQUIRES_WHITELIST'):
1114
- needs_enabled = obj.REQUIRES_WHITELIST
1115
- display.deprecated("The VarsModule class variable 'REQUIRES_WHITELIST' is deprecated. "
1116
- "Use 'REQUIRES_ENABLED' instead.", version=2.18)
1117
1113
  if not needs_enabled:
1118
1114
  # Use get_with_context to cache the plugin the first time we see it.
1119
1115
  self.get_with_context(fqcn)[0]
@@ -1424,13 +1420,6 @@ def _load_plugin_filter():
1424
1420
  # Modules and action plugins share the same reject list since the difference between the
1425
1421
  # two isn't visible to the users
1426
1422
  if version == u'1.0':
1427
-
1428
- if 'module_blacklist' in filter_data:
1429
- display.deprecated("'module_blacklist' is being removed in favor of 'module_rejectlist'", version='2.18')
1430
- if 'module_rejectlist' not in filter_data:
1431
- filter_data['module_rejectlist'] = filter_data['module_blacklist']
1432
- del filter_data['module_blacklist']
1433
-
1434
1423
  try:
1435
1424
  filters['ansible.modules'] = frozenset(filter_data['module_rejectlist'])
1436
1425
  except TypeError:
@@ -8,7 +8,7 @@ DOCUMENTATION = """
8
8
  version_added: "2.5"
9
9
  short_description: Display the 'resolved' Ansible option values.
10
10
  description:
11
- - Retrieves the value of an Ansible configuration setting, resolving all sources, from defaults, ansible.cfg, envirionmnet,
11
+ - Retrieves the value of an Ansible configuration setting, resolving all sources, from defaults, ansible.cfg, environment,
12
12
  CLI, and variables, but not keywords.
13
13
  - The values returned assume the context of the current host or C(inventory_hostname).
14
14
  - You can use C(ansible-config list) to see the global available settings, add C(-t all) to also show plugin options.
@@ -52,18 +52,30 @@ EXAMPLES = """
52
52
  - name: msg="Match 'Li' on the first column, but return the 3rd column (columns start counting after the match)"
53
53
  ansible.builtin.debug: msg="The atomic mass of Lithium is {{ lookup('ansible.builtin.csvfile', 'Li file=elements.csv delimiter=, col=2') }}"
54
54
 
55
- - name: Define Values From CSV File, this reads file in one go, but you could also use col= to read each in it's own lookup.
55
+ # Contents of bgp_neighbors.csv
56
+ # 127.0.0.1,10.0.0.1,24,nones,lola,pepe,127.0.0.2
57
+ # 128.0.0.1,10.1.0.1,20,notes,lolita,pepito,128.0.0.2
58
+ # 129.0.0.1,10.2.0.1,23,nines,aayush,pepete,129.0.0.2
59
+
60
+ - name: Define values from CSV file, this reads file in one go, but you could also use col= to read each in it's own lookup.
56
61
  ansible.builtin.set_fact:
57
- loop_ip: "{{ csvline[0] }}"
58
- int_ip: "{{ csvline[1] }}"
59
- int_mask: "{{ csvline[2] }}"
60
- int_name: "{{ csvline[3] }}"
61
- local_as: "{{ csvline[4] }}"
62
- neighbor_as: "{{ csvline[5] }}"
63
- neigh_int_ip: "{{ csvline[6] }}"
62
+ '{{ columns[item|int] }}': "{{ csvline }}"
64
63
  vars:
65
- csvline: "{{ lookup('ansible.builtin.csvfile', bgp_neighbor_ip, file='bgp_neighbors.csv', delimiter=',') }}"
64
+ csvline: "{{ lookup('csvfile', bgp_neighbor_ip, file='bgp_neighbors.csv', delimiter=',', col=item) }}"
65
+ columns: ['loop_ip', 'int_ip', 'int_mask', 'int_name', 'local_as', 'neighbour_as', 'neight_int_ip']
66
+ bgp_neighbor_ip: '127.0.0.1'
67
+ loop: '{{ range(columns|length|int) }}'
66
68
  delegate_to: localhost
69
+ delegate_facts: true
70
+
71
+ # Contents of people.csv
72
+ # # Last,First,Email,Extension
73
+ # Smith,Jane,jsmith@example.com,1234
74
+
75
+ - name: Specify the column (by keycol) in which the string should be searched
76
+ assert:
77
+ that:
78
+ - lookup('ansible.builtin.csvfile', 'Jane', file='people.csv', delimiter=',', col=0, keycol=1) == "Smith"
67
79
 
68
80
  # Contents of debug.csv
69
81
  # test1 ret1.1 ret2.1
@@ -30,22 +30,21 @@ EXAMPLES = """
30
30
  ansible.builtin.debug:
31
31
  msg: "'{{ lookup('ansible.builtin.env', 'HOME') }}' is the HOME environment variable."
32
32
 
33
- - name: Before 2.13, how to set default value if the variable is not defined.
34
- This cannot distinguish between USR undefined and USR=''.
33
+ - name: Before 2.13, how to set default value if the variable is not defined
35
34
  ansible.builtin.debug:
36
- msg: "{{ lookup('ansible.builtin.env', 'USR')|default('nobody', True) }} is the user."
35
+ msg: "Hello {{ lookup('ansible.builtin.env', 'UNDEFINED_VARIABLE') | default('World', True) }}"
37
36
 
38
- - name: Example how to set default value if the variable is not defined, ignores USR=''
37
+ - name: Example how to set default value if the variable is not defined
39
38
  ansible.builtin.debug:
40
- msg: "{{ lookup('ansible.builtin.env', 'USR', default='nobody') }} is the user."
39
+ msg: "Hello {{ lookup('ansible.builtin.env', 'UNDEFINED_VARIABLE', default='World') }}"
41
40
 
42
- - name: Set default value to Undefined, if the variable is not defined
41
+ - name: Fail if the variable is not defined by setting default value to 'Undefined'
43
42
  ansible.builtin.debug:
44
- msg: "{{ lookup('ansible.builtin.env', 'USR', default=Undefined) }} is the user."
43
+ msg: "Hello {{ lookup('ansible.builtin.env', 'UNDEFINED_VARIABLE', default=Undefined) }}"
45
44
 
46
- - name: Set default value to undef(), if the variable is not defined
45
+ - name: Fail if the variable is not defined by setting default value to 'undef()'
47
46
  ansible.builtin.debug:
48
- msg: "{{ lookup('ansible.builtin.env', 'USR', default=undef()) }} is the user."
47
+ msg: "Hello {{ lookup('ansible.builtin.env', 'UNDEFINED_VARIABLE', default=undef()) }}"
49
48
  """
50
49
 
51
50
  RETURN = """
@@ -49,6 +49,12 @@ DOCUMENTATION = """
49
49
  default: False
50
50
  aliases: ['allow_none']
51
51
  version_added: '2.12'
52
+ interpolation:
53
+ description:
54
+ Allows for interpolation of values, see https://docs.python.org/3/library/configparser.html#configparser.BasicInterpolation
55
+ type: bool
56
+ default: True
57
+ version_added: '2.18'
52
58
  seealso:
53
59
  - ref: playbook_task_paths
54
60
  description: Search paths used for relative files.
@@ -140,7 +146,10 @@ class LookupModule(LookupBase):
140
146
  self.set_options(var_options=variables, direct=kwargs)
141
147
  paramvals = self.get_options()
142
148
 
143
- self.cp = configparser.ConfigParser(allow_no_value=paramvals.get('allow_no_value', paramvals.get('allow_none')))
149
+ self.cp = configparser.ConfigParser(
150
+ allow_no_value=paramvals.get('allow_no_value', paramvals.get('allow_none')),
151
+ interpolation=configparser.BasicInterpolation() if paramvals.get('interpolation') else None,
152
+ )
144
153
  if paramvals['case_sensitive']:
145
154
  self.cp.optionxform = to_native
146
155
 
@@ -31,7 +31,7 @@ RETURN = """
31
31
  - random item
32
32
  type: raw
33
33
  """
34
- import random
34
+ import secrets
35
35
 
36
36
  from ansible.errors import AnsibleError
37
37
  from ansible.module_utils.common.text.converters import to_native
@@ -45,7 +45,7 @@ class LookupModule(LookupBase):
45
45
  ret = terms
46
46
  if terms:
47
47
  try:
48
- ret = [random.choice(terms)]
48
+ ret = [secrets.choice(terms)]
49
49
  except Exception as e:
50
50
  raise AnsibleError("Unable to choose random term: %s" % to_native(e))
51
51
 
@@ -103,8 +103,8 @@ options:
103
103
  none: Will not follow any redirects.
104
104
  safe: Only redirects doing GET or HEAD requests will be followed.
105
105
  urllib2: Defer to urllib2 behavior (As of writing this follows HTTP redirects).
106
- 'no': (DEPRECATED, will be removed in the future version) alias of V(none).
107
- 'yes': (DEPRECATED, will be removed in the future version) alias of V(all).
106
+ 'no': (DEPRECATED, removed in 2.22) alias of V(none).
107
+ 'yes': (DEPRECATED, removed in 2.22) alias of V(all).
108
108
  use_gssapi:
109
109
  description:
110
110
  - Use GSSAPI handler of requests
@@ -234,6 +234,11 @@ class LookupModule(LookupBase):
234
234
  ret = []
235
235
  for term in terms:
236
236
  display.vvvv("url lookup connecting to %s" % term)
237
+ if self.get_option('follow_redirects') in ('yes', 'no'):
238
+ display.deprecated(
239
+ "Using 'yes' or 'no' for 'follow_redirects' parameter is deprecated.",
240
+ version='2.22'
241
+ )
237
242
  try:
238
243
  response = open_url(
239
244
  term, validate_certs=self.get_option('validate_certs'),
@@ -18,8 +18,8 @@ from __future__ import annotations
18
18
 
19
19
  import os
20
20
  import os.path
21
- import random
22
21
  import re
22
+ import secrets
23
23
  import shlex
24
24
  import time
25
25
 
@@ -38,6 +38,9 @@ class ShellBase(AnsiblePlugin):
38
38
 
39
39
  super(ShellBase, self).__init__()
40
40
 
41
+ # Not used but here for backwards compatibility.
42
+ # ansible.posix.fish uses (but does not actually use) this value.
43
+ # https://github.com/ansible-collections/ansible.posix/blob/f41f08e9e3d3129e709e122540b5ae6bc19932be/plugins/shell/fish.py#L38-L39
41
44
  self.env = {}
42
45
  self.tmpdir = None
43
46
  self.executable = None
@@ -60,18 +63,6 @@ class ShellBase(AnsiblePlugin):
60
63
 
61
64
  super(ShellBase, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
62
65
 
63
- # set env if needed, deal with environment's 'dual nature' list of dicts or dict
64
- # TODO: config system should already resolve this so we should be able to just iterate over dicts
65
- env = self.get_option('environment')
66
- if isinstance(env, string_types):
67
- raise AnsibleError('The "environment" keyword takes a list of dictionaries or a dictionary, not a string')
68
- if not isinstance(env, Sequence):
69
- env = [env]
70
- for env_dict in env:
71
- if not isinstance(env_dict, Mapping):
72
- raise AnsibleError('The "environment" keyword takes a list of dictionaries (or single dictionary), but got a "%s" instead' % type(env_dict))
73
- self.env.update(env_dict)
74
-
75
66
  # We can remove the try: except in the future when we make ShellBase a proper subset of
76
67
  # *all* shells. Right now powershell and third party shells which do not use the
77
68
  # shell_common documentation fragment (and so do not have system_tmpdirs) will fail
@@ -82,7 +73,7 @@ class ShellBase(AnsiblePlugin):
82
73
 
83
74
  @staticmethod
84
75
  def _generate_temp_dir_name():
85
- return 'ansible-tmp-%s-%s-%s' % (time.time(), os.getpid(), random.randint(0, 2**48))
76
+ return 'ansible-tmp-%s-%s-%s' % (time.time(), os.getpid(), secrets.randbelow(2**48))
86
77
 
87
78
  def env_prefix(self, **kwargs):
88
79
  return ' '.join(['%s=%s' % (k, self.quote(text_type(v))) for k, v in kwargs.items()])
@@ -101,23 +92,23 @@ class ShellBase(AnsiblePlugin):
101
92
  def chmod(self, paths, mode):
102
93
  cmd = ['chmod', mode]
103
94
  cmd.extend(paths)
104
- return shlex.join(cmd)
95
+ return self.join(cmd)
105
96
 
106
97
  def chown(self, paths, user):
107
98
  cmd = ['chown', user]
108
99
  cmd.extend(paths)
109
- return shlex.join(cmd)
100
+ return self.join(cmd)
110
101
 
111
102
  def chgrp(self, paths, group):
112
103
  cmd = ['chgrp', group]
113
104
  cmd.extend(paths)
114
- return shlex.join(cmd)
105
+ return self.join(cmd)
115
106
 
116
107
  def set_user_facl(self, paths, user, mode):
117
108
  """Only sets acls for users as that's really all we need"""
118
109
  cmd = ['setfacl', '-m', 'u:%s:%s' % (user, mode)]
119
110
  cmd.extend(paths)
120
- return shlex.join(cmd)
111
+ return self.join(cmd)
121
112
 
122
113
  def remove(self, path, recurse=False):
123
114
  path = self.quote(path)
@@ -138,7 +129,7 @@ class ShellBase(AnsiblePlugin):
138
129
  # other users can read and access the tmp directory.
139
130
  # This is because we use system to create tmp dirs for unprivileged users who are
140
131
  # sudo'ing to a second unprivileged user.
141
- # The 'system_tmpdirs' setting defines dirctories we can use for this purpose
132
+ # The 'system_tmpdirs' setting defines directories we can use for this purpose
142
133
  # the default are, /tmp and /var/tmp.
143
134
  # So we only allow one of those locations if system=True, using the
144
135
  # passed in tmpdir if it is valid or the first one from the setting if not.
@@ -211,7 +202,7 @@ class ShellBase(AnsiblePlugin):
211
202
  arg_path,
212
203
  ]
213
204
 
214
- cleaned_up_cmd = shlex.join(
205
+ cleaned_up_cmd = self.join(
215
206
  stripped_cmd_part for raw_cmd_part in cmd_parts
216
207
  if raw_cmd_part and (stripped_cmd_part := raw_cmd_part.strip())
217
208
  )
@@ -232,3 +223,7 @@ class ShellBase(AnsiblePlugin):
232
223
  def quote(self, cmd):
233
224
  """Returns a shell-escaped string that can be safely used as one token in a shell command line"""
234
225
  return shlex.quote(cmd)
226
+
227
+ def join(self, cmd_parts):
228
+ """Returns a shell-escaped string from a list that can be safely used in a shell command line"""
229
+ return shlex.join(cmd_parts)
@@ -100,6 +100,8 @@ class ShellModule(ShellBase):
100
100
  # Family of shells this has. Must match the filename without extension
101
101
  SHELL_FAMILY = 'powershell'
102
102
 
103
+ # We try catch as some connection plugins don't have a console (PSRP).
104
+ _CONSOLE_ENCODING = "try { [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding } catch {}"
103
105
  _SHELL_REDIRECT_ALLNULL = '> $null'
104
106
  _SHELL_AND = ';'
105
107
 
@@ -157,13 +159,14 @@ class ShellModule(ShellBase):
157
159
  if not basefile:
158
160
  basefile = self.__class__._generate_temp_dir_name()
159
161
  basefile = self._escape(self._unquote(basefile))
160
- basetmpdir = tmpdir if tmpdir else self.get_option('remote_tmp')
162
+ basetmpdir = self._escape(tmpdir if tmpdir else self.get_option('remote_tmp'))
161
163
 
162
- script = '''
163
- $tmp_path = [System.Environment]::ExpandEnvironmentVariables('%s')
164
- $tmp = New-Item -Type Directory -Path $tmp_path -Name '%s'
164
+ script = f'''
165
+ {self._CONSOLE_ENCODING}
166
+ $tmp_path = [System.Environment]::ExpandEnvironmentVariables('{basetmpdir}')
167
+ $tmp = New-Item -Type Directory -Path $tmp_path -Name '{basefile}'
165
168
  Write-Output -InputObject $tmp.FullName
166
- ''' % (basetmpdir, basefile)
169
+ '''
167
170
  return self._encode_script(script.strip())
168
171
 
169
172
  def expand_user(self, user_home_path, username=''):
@@ -177,7 +180,7 @@ class ShellModule(ShellBase):
177
180
  script = "Write-Output ((Get-Location).Path + '%s')" % self._escape(user_home_path[1:])
178
181
  else:
179
182
  script = "Write-Output '%s'" % self._escape(user_home_path)
180
- return self._encode_script(script)
183
+ return self._encode_script(f"{self._CONSOLE_ENCODING}; {script}")
181
184
 
182
185
  def exists(self, path):
183
186
  path = self._escape(self._unquote(path))
@@ -646,7 +646,7 @@ class StrategyBase:
646
646
  for result_item in result_items:
647
647
  if '_ansible_notify' in result_item and task_result.is_changed():
648
648
  # only ensure that notified handlers exist, if so save the notifications for when
649
- # handlers are actually flushed so the last defined handlers are exexcuted,
649
+ # handlers are actually flushed so the last defined handlers are executed,
650
650
  # otherwise depending on the setting either error or warn
651
651
  host_state = iterator.get_state_for_host(original_host.name)
652
652
  for notification in result_item['_ansible_notify']:
@@ -926,6 +926,8 @@ class StrategyBase:
926
926
  meta_action = task.args.get('_raw_params')
927
927
 
928
928
  def _evaluate_conditional(h):
929
+ if not task.when:
930
+ return True
929
931
  all_vars = self._variable_manager.get_vars(play=iterator._play, host=h, task=task,
930
932
  _hosts=self._hosts_cache, _hosts_all=self._hosts_cache_all)
931
933
  templar = Templar(loader=self._loader, variables=all_vars)
@@ -993,7 +995,7 @@ class StrategyBase:
993
995
  if _evaluate_conditional(target_host):
994
996
  for host in self._inventory.get_hosts(iterator._play.hosts):
995
997
  if host.name not in self._tqm._unreachable_hosts:
996
- iterator.set_run_state_for_host(host.name, IteratingStates.COMPLETE)
998
+ iterator.end_host(host.name)
997
999
  msg = "ending batch"
998
1000
  else:
999
1001
  skipped = True
@@ -1002,7 +1004,7 @@ class StrategyBase:
1002
1004
  if _evaluate_conditional(target_host):
1003
1005
  for host in self._inventory.get_hosts(iterator._play.hosts):
1004
1006
  if host.name not in self._tqm._unreachable_hosts:
1005
- iterator.set_run_state_for_host(host.name, IteratingStates.COMPLETE)
1007
+ iterator.end_host(host.name)
1006
1008
  # end_play is used in PlaybookExecutor/TQM to indicate that
1007
1009
  # the whole play is supposed to be ended as opposed to just a batch
1008
1010
  iterator.end_play = True
@@ -1012,8 +1014,7 @@ class StrategyBase:
1012
1014
  skip_reason += ', continuing play'
1013
1015
  elif meta_action == 'end_host':
1014
1016
  if _evaluate_conditional(target_host):
1015
- iterator.set_run_state_for_host(target_host.name, IteratingStates.COMPLETE)
1016
- iterator._play._removed_hosts.append(target_host.name)
1017
+ iterator.end_host(target_host.name)
1017
1018
  msg = "ending play for %s" % target_host.name
1018
1019
  else:
1019
1020
  skipped = True
@@ -1021,13 +1022,23 @@ class StrategyBase:
1021
1022
  # TODO: Nix msg here? Left for historical reasons, but skip_reason exists now.
1022
1023
  msg = "end_host conditional evaluated to false, continuing execution for %s" % target_host.name
1023
1024
  elif meta_action == 'role_complete':
1024
- # Allow users to use this in a play as reported in https://github.com/ansible/ansible/issues/22286?
1025
- # How would this work with allow_duplicates??
1026
1025
  if task.implicit:
1027
1026
  role_obj = self._get_cached_role(task, iterator._play)
1028
1027
  if target_host.name in role_obj._had_task_run:
1029
1028
  role_obj._completed[target_host.name] = True
1030
1029
  msg = 'role_complete for %s' % target_host.name
1030
+ elif meta_action == 'end_role':
1031
+ if _evaluate_conditional(target_host):
1032
+ while True:
1033
+ state, task = iterator.get_next_task_for_host(target_host, peek=True)
1034
+ if task.action in C._ACTION_META and task.args.get("_raw_params") == "role_complete":
1035
+ break
1036
+ iterator.set_state_for_host(target_host.name, state)
1037
+ display.debug("'%s' skipped because role has been ended via 'end_role'" % task)
1038
+ msg = 'ending role %s for %s' % (task._role.get_name(), target_host.name)
1039
+ else:
1040
+ skipped = True
1041
+ skip_reason += 'continuing role %s for %s' % (task._role.get_name(), target_host.name)
1031
1042
  elif meta_action == 'reset_connection':
1032
1043
  all_vars = self._variable_manager.get_vars(play=iterator._play, host=target_host, task=task,
1033
1044
  _hosts=self._hosts_cache, _hosts_all=self._hosts_cache_all)
@@ -34,7 +34,6 @@ from ansible.errors import AnsibleError, AnsibleAssertionError, AnsibleParserErr
34
34
  from ansible.module_utils.common.text.converters import to_text
35
35
  from ansible.playbook.handler import Handler
36
36
  from ansible.playbook.included_file import IncludedFile
37
- from ansible.playbook.task import Task
38
37
  from ansible.plugins.loader import action_loader
39
38
  from ansible.plugins.strategy import StrategyBase
40
39
  from ansible.template import Templar
@@ -51,12 +50,6 @@ class StrategyModule(StrategyBase):
51
50
  be a noop task to keep the iterator in lock step across
52
51
  all hosts.
53
52
  '''
54
- noop_task = Task()
55
- noop_task.action = 'meta'
56
- noop_task.args['_raw_params'] = 'noop'
57
- noop_task.implicit = True
58
- noop_task.set_loader(iterator._play._loader)
59
-
60
53
  state_task_per_host = {}
61
54
  for host in hosts:
62
55
  state, task = iterator.get_next_task_for_host(host, peek=True)
@@ -64,7 +57,7 @@ class StrategyModule(StrategyBase):
64
57
  state_task_per_host[host] = state, task
65
58
 
66
59
  if not state_task_per_host:
67
- return [(h, None) for h in hosts]
60
+ return []
68
61
 
69
62
  task_uuids = {t._uuid for s, t in state_task_per_host.values()}
70
63
  _loop_cnt = 0
@@ -90,8 +83,6 @@ class StrategyModule(StrategyBase):
90
83
  if cur_task._uuid == task._uuid:
91
84
  iterator.set_state_for_host(host.name, state)
92
85
  host_tasks.append((host, task))
93
- else:
94
- host_tasks.append((host, noop_task))
95
86
 
96
87
  if cur_task.action in C._ACTION_META and cur_task.args.get('_raw_params') == 'flush_handlers':
97
88
  iterator.all_tasks[iterator.cur_task:iterator.cur_task] = [h for b in iterator._play.handlers for h in b.block]
@@ -133,9 +124,6 @@ class StrategyModule(StrategyBase):
133
124
 
134
125
  results = []
135
126
  for (host, task) in host_tasks:
136
- if not task:
137
- continue
138
-
139
127
  if self._tqm._terminated:
140
128
  break
141
129
 
@@ -25,8 +25,9 @@ from collections.abc import MutableMapping, MutableSequence
25
25
  from ansible.module_utils.compat.version import LooseVersion, StrictVersion
26
26
 
27
27
  from ansible import errors
28
- from ansible.module_utils.common.text.converters import to_native, to_text
28
+ from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
29
29
  from ansible.module_utils.parsing.convert_bool import boolean
30
+ from ansible.parsing.vault import is_encrypted_file
30
31
  from ansible.utils.display import Display
31
32
  from ansible.utils.version import SemanticVersion
32
33
 
@@ -39,6 +40,13 @@ except ImportError:
39
40
  display = Display()
40
41
 
41
42
 
43
+ def timedout(result):
44
+ ''' Test if task result yields a time out'''
45
+ if not isinstance(result, MutableMapping):
46
+ raise errors.AnsibleFilterError("The 'timedout' test expects a dictionary")
47
+ return result.get('timedout', False) and result['timedout'].get('period', False)
48
+
49
+
42
50
  def failed(result):
43
51
  ''' Test if task result yields failed '''
44
52
  if not isinstance(result, MutableMapping):
@@ -143,6 +151,18 @@ def vault_encrypted(value):
143
151
  return getattr(value, '__ENCRYPTED__', False) and value.is_encrypted()
144
152
 
145
153
 
154
+ def vaulted_file(value):
155
+ """Evaluate whether a file is a vault
156
+
157
+ .. versionadded:: 2.18
158
+ """
159
+ try:
160
+ with open(to_bytes(value), 'rb') as f:
161
+ return is_encrypted_file(f)
162
+ except (OSError, IOError) as e:
163
+ raise errors.AnsibleFilterError(f"Cannot test if the file {value} is a vault", orig_exc=e)
164
+
165
+
146
166
  def match(value, pattern='', ignorecase=False, multiline=False):
147
167
  ''' Perform a `re.match` returning a boolean '''
148
168
  return regex(value, pattern, ignorecase, multiline, 'match')
@@ -250,6 +270,7 @@ class TestModule(object):
250
270
  'successful': success,
251
271
  'reachable': reachable,
252
272
  'unreachable': unreachable,
273
+ 'timedout': timedout,
253
274
 
254
275
  # changed testing
255
276
  'changed': changed,
@@ -282,4 +303,5 @@ class TestModule(object):
282
303
 
283
304
  # vault
284
305
  'vault_encrypted': vault_encrypted,
306
+ 'vaulted_file': vaulted_file,
285
307
  }
@@ -19,7 +19,7 @@ DOCUMENTATION:
19
19
  required: True
20
20
  EXAMPLES: |
21
21
  big: [1,2,3,4,5]
22
- sml: [3,4]
22
+ small: [3,4]
23
23
  issmallinbig: '{{ small is subset(big) }}'
24
24
  RETURN:
25
25
  _value:
@@ -19,7 +19,7 @@ DOCUMENTATION:
19
19
  required: True
20
20
  EXAMPLES: |
21
21
  big: [1,2,3,4,5]
22
- sml: [3,4]
22
+ small: [3,4]
23
23
  issmallinbig: '{{ small is subset(big) }}'
24
24
  RETURN:
25
25
  _value:
@@ -0,0 +1,20 @@
1
+ DOCUMENTATION:
2
+ name: timedout
3
+ author: Ansible Core
4
+ version_added: "2.18"
5
+ short_description: did the task time out
6
+ description:
7
+ - Tests if task finished ended due to a time out
8
+ options:
9
+ _input:
10
+ description: registered result from an Ansible task
11
+ type: dictionary
12
+ required: True
13
+ EXAMPLES: |
14
+ # test 'status' to know how to respond
15
+ {{ taskresults is timedout }}
16
+
17
+ RETURN:
18
+ _value:
19
+ description: A dictionary with 2 keys 'frame' showing the 'frame of code' in which the timeout occurred and 'period' with the time limit that was enforced.
20
+ type: dict
@@ -1,18 +1,18 @@
1
1
  DOCUMENTATION:
2
- name: truthy
2
+ name: vault_encrypted
3
3
  author: Ansible Core
4
4
  version_added: "2.10"
5
- short_description: Is this an encrypted vault
5
+ short_description: Is this an encrypted vault string
6
6
  description:
7
- - Verifies if the input is an Ansible vault.
7
+ - Verifies if the input string is an Ansible vault or not
8
8
  options:
9
9
  _input:
10
- description: The possible vault.
10
+ description: The possible vault string
11
11
  type: string
12
12
  required: True
13
13
  EXAMPLES: |
14
- thisisfalse: '{{ "any string" is ansible_vault }}'
15
- thisistrue: '{{ "$ANSIBLE_VAULT;1.2;AES256;dev...." is ansible_vault }}'
14
+ thisisfalse: '{{ "any string" is vault_encryped}}'
15
+ thisistrue: '{{ "$ANSIBLE_VAULT;1.2;AES256;dev...." is vault_encrypted}}'
16
16
  RETURN:
17
17
  _value:
18
18
  description: Returns V(True) if the input is a valid ansible vault, V(False) otherwise.
@@ -0,0 +1,19 @@
1
+ DOCUMENTATION:
2
+ name: vaulted_file
3
+ author: Ansible Core
4
+ version_added: "2.18"
5
+ short_description: Is this file an encrypted vault
6
+ description:
7
+ - Verifies if the input path is an Ansible vault file.
8
+ options:
9
+ _input:
10
+ description: The path to the possible vault.
11
+ type: path
12
+ required: True
13
+ EXAMPLES: |
14
+ thisisfalse: '{{ "/etc/hosts" is vaulted_file}}'
15
+ thisistrue: '{{ "/path/to/vaulted/file" is vaulted_file}}'
16
+ RETURN:
17
+ _value:
18
+ description: Returns V(True) if the path is a valid ansible vault, V(False) otherwise.
19
+ type: boolean
ansible/release.py CHANGED
@@ -17,6 +17,6 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = '2.17.5rc1'
20
+ __version__ = '2.18.0rc1'
21
21
  __author__ = 'Ansible, Inc.'
22
- __codename__ = "Gallows Pole"
22
+ __codename__ = "Fool in the Rain"