ansible-core 2.19.3rc1__py3-none-any.whl → 2.20.0b2__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 (201) hide show
  1. ansible/_internal/__init__.py +1 -4
  2. ansible/_internal/_ansiballz/_builder.py +1 -3
  3. ansible/_internal/_collection_proxy.py +7 -9
  4. ansible/_internal/_json/__init__.py +3 -4
  5. ansible/_internal/_templating/_engine.py +1 -1
  6. ansible/_internal/_templating/_jinja_plugins.py +1 -2
  7. ansible/_internal/_wrapt.py +105 -301
  8. ansible/cli/__init__.py +11 -10
  9. ansible/cli/adhoc.py +1 -2
  10. ansible/cli/arguments/option_helpers.py +1 -1
  11. ansible/cli/config.py +5 -6
  12. ansible/cli/doc.py +70 -68
  13. ansible/cli/galaxy.py +15 -24
  14. ansible/cli/inventory.py +0 -1
  15. ansible/cli/playbook.py +0 -1
  16. ansible/cli/pull.py +0 -1
  17. ansible/cli/scripts/ansible_connection_cli_stub.py +1 -1
  18. ansible/collections/list.py +4 -2
  19. ansible/config/base.yml +1 -25
  20. ansible/config/manager.py +0 -2
  21. ansible/executor/play_iterator.py +42 -20
  22. ansible/executor/playbook_executor.py +0 -9
  23. ansible/executor/task_executor.py +26 -18
  24. ansible/executor/task_queue_manager.py +1 -3
  25. ansible/galaxy/api.py +33 -80
  26. ansible/galaxy/collection/__init__.py +4 -17
  27. ansible/galaxy/dependency_resolution/dataclasses.py +0 -10
  28. ansible/galaxy/dependency_resolution/providers.py +24 -118
  29. ansible/galaxy/role.py +1 -33
  30. ansible/inventory/manager.py +2 -3
  31. ansible/keyword_desc.yml +0 -3
  32. ansible/module_utils/_internal/_datatag/__init__.py +2 -10
  33. ansible/module_utils/_internal/_no_six.py +86 -0
  34. ansible/module_utils/_text.py +28 -8
  35. ansible/module_utils/ansible_release.py +2 -2
  36. ansible/module_utils/basic.py +26 -23
  37. ansible/module_utils/common/_collections_compat.py +11 -2
  38. ansible/module_utils/common/collections.py +8 -3
  39. ansible/module_utils/common/dict_transformations.py +1 -2
  40. ansible/module_utils/common/network.py +4 -2
  41. ansible/module_utils/common/parameters.py +32 -41
  42. ansible/module_utils/common/text/converters.py +109 -23
  43. ansible/module_utils/common/text/formatters.py +6 -2
  44. ansible/module_utils/common/validation.py +11 -9
  45. ansible/module_utils/connection.py +8 -3
  46. ansible/module_utils/facts/hardware/linux.py +23 -7
  47. ansible/module_utils/facts/hardware/netbsd.py +1 -1
  48. ansible/module_utils/facts/hardware/sunos.py +2 -1
  49. ansible/module_utils/facts/packages.py +6 -2
  50. ansible/module_utils/facts/system/distribution.py +2 -1
  51. ansible/module_utils/facts/system/env.py +6 -3
  52. ansible/module_utils/facts/system/local.py +3 -1
  53. ansible/module_utils/parsing/convert_bool.py +6 -2
  54. ansible/module_utils/service.py +2 -3
  55. ansible/module_utils/six/__init__.py +19 -6
  56. ansible/module_utils/yumdnf.py +0 -5
  57. ansible/modules/apt.py +18 -13
  58. ansible/modules/apt_repository.py +1 -1
  59. ansible/modules/assemble.py +5 -9
  60. ansible/modules/blockinfile.py +39 -23
  61. ansible/modules/cron.py +26 -35
  62. ansible/modules/deb822_repository.py +83 -12
  63. ansible/modules/dnf.py +3 -7
  64. ansible/modules/dnf5.py +4 -6
  65. ansible/modules/expect.py +0 -3
  66. ansible/modules/find.py +1 -2
  67. ansible/modules/get_url.py +1 -1
  68. ansible/modules/git.py +4 -5
  69. ansible/modules/include_vars.py +1 -1
  70. ansible/modules/known_hosts.py +7 -1
  71. ansible/modules/lineinfile.py +71 -63
  72. ansible/modules/package_facts.py +1 -1
  73. ansible/modules/pip.py +8 -2
  74. ansible/modules/replace.py +6 -6
  75. ansible/modules/service.py +3 -4
  76. ansible/modules/stat.py +20 -0
  77. ansible/modules/uri.py +9 -10
  78. ansible/modules/user.py +1 -2
  79. ansible/modules/wait_for.py +2 -2
  80. ansible/modules/wait_for_connection.py +2 -1
  81. ansible/modules/yum_repository.py +1 -16
  82. ansible/parsing/dataloader.py +24 -31
  83. ansible/parsing/mod_args.py +3 -0
  84. ansible/parsing/vault/__init__.py +1 -2
  85. ansible/playbook/base.py +8 -56
  86. ansible/playbook/block.py +1 -63
  87. ansible/playbook/collectionsearch.py +1 -2
  88. ansible/playbook/handler.py +1 -7
  89. ansible/playbook/helpers.py +15 -20
  90. ansible/playbook/included_file.py +1 -1
  91. ansible/playbook/play.py +105 -49
  92. ansible/playbook/play_context.py +4 -0
  93. ansible/playbook/role/__init__.py +10 -65
  94. ansible/playbook/role/definition.py +3 -4
  95. ansible/playbook/role/include.py +2 -3
  96. ansible/playbook/role/metadata.py +1 -12
  97. ansible/playbook/role/requirement.py +1 -2
  98. ansible/playbook/role_include.py +1 -2
  99. ansible/playbook/taggable.py +16 -5
  100. ansible/playbook/task.py +51 -55
  101. ansible/plugins/action/__init__.py +20 -19
  102. ansible/plugins/action/add_host.py +1 -2
  103. ansible/plugins/action/fetch.py +3 -5
  104. ansible/plugins/action/group_by.py +1 -2
  105. ansible/plugins/action/include_vars.py +20 -22
  106. ansible/plugins/action/script.py +1 -3
  107. ansible/plugins/action/template.py +1 -2
  108. ansible/plugins/action/uri.py +4 -2
  109. ansible/plugins/cache/__init__.py +1 -0
  110. ansible/plugins/callback/__init__.py +13 -6
  111. ansible/plugins/connection/__init__.py +3 -7
  112. ansible/plugins/connection/local.py +2 -3
  113. ansible/plugins/connection/psrp.py +0 -2
  114. ansible/plugins/connection/ssh.py +2 -7
  115. ansible/plugins/connection/winrm.py +0 -2
  116. ansible/plugins/doc_fragments/result_format_callback.py +15 -0
  117. ansible/plugins/filter/core.py +4 -5
  118. ansible/plugins/filter/encryption.py +3 -27
  119. ansible/plugins/filter/mathstuff.py +1 -2
  120. ansible/plugins/filter/to_nice_yaml.yml +31 -3
  121. ansible/plugins/filter/to_yaml.yml +29 -12
  122. ansible/plugins/inventory/__init__.py +1 -2
  123. ansible/plugins/inventory/toml.py +3 -6
  124. ansible/plugins/inventory/yaml.py +1 -2
  125. ansible/plugins/loader.py +3 -4
  126. ansible/plugins/lookup/password.py +1 -2
  127. ansible/plugins/lookup/subelements.py +2 -3
  128. ansible/plugins/lookup/url.py +1 -1
  129. ansible/plugins/lookup/varnames.py +1 -2
  130. ansible/plugins/shell/__init__.py +9 -4
  131. ansible/plugins/shell/powershell.py +8 -24
  132. ansible/plugins/strategy/__init__.py +6 -3
  133. ansible/plugins/test/core.py +4 -1
  134. ansible/plugins/test/falsy.yml +1 -1
  135. ansible/plugins/test/regex.yml +18 -6
  136. ansible/plugins/test/truthy.yml +1 -1
  137. ansible/release.py +2 -2
  138. ansible/template/__init__.py +3 -7
  139. ansible/utils/collection_loader/_collection_config.py +5 -0
  140. ansible/utils/collection_loader/_collection_finder.py +11 -14
  141. ansible/utils/context_objects.py +7 -4
  142. ansible/utils/display.py +7 -6
  143. ansible/utils/encrypt.py +0 -5
  144. ansible/utils/helpers.py +6 -2
  145. ansible/utils/jsonrpc.py +7 -3
  146. ansible/utils/plugin_docs.py +49 -38
  147. ansible/utils/ssh_functions.py +0 -19
  148. ansible/utils/unsafe_proxy.py +7 -7
  149. ansible/vars/clean.py +2 -3
  150. ansible/vars/manager.py +28 -22
  151. ansible/vars/plugins.py +1 -31
  152. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/METADATA +3 -3
  153. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/RECORD +199 -200
  154. ansible_test/_data/completion/docker.txt +7 -7
  155. ansible_test/_data/completion/network.txt +0 -1
  156. ansible_test/_data/completion/remote.txt +4 -4
  157. ansible_test/_data/requirements/ansible-test.txt +1 -1
  158. ansible_test/_data/requirements/sanity.changelog.txt +1 -1
  159. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  160. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  161. ansible_test/_internal/cache.py +2 -5
  162. ansible_test/_internal/cli/compat.py +1 -1
  163. ansible_test/_internal/commands/coverage/combine.py +1 -3
  164. ansible_test/_internal/commands/integration/__init__.py +3 -7
  165. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  166. ansible_test/_internal/commands/integration/coverage.py +1 -3
  167. ansible_test/_internal/commands/integration/filters.py +5 -10
  168. ansible_test/_internal/commands/sanity/validate_modules.py +1 -5
  169. ansible_test/_internal/commands/units/__init__.py +1 -13
  170. ansible_test/_internal/completion.py +2 -5
  171. ansible_test/_internal/config.py +2 -7
  172. ansible_test/_internal/coverage_util.py +1 -1
  173. ansible_test/_internal/delegation.py +2 -0
  174. ansible_test/_internal/docker_util.py +1 -1
  175. ansible_test/_internal/host_profiles.py +6 -11
  176. ansible_test/_internal/provider/__init__.py +2 -5
  177. ansible_test/_internal/provisioning.py +2 -5
  178. ansible_test/_internal/pypi_proxy.py +1 -1
  179. ansible_test/_internal/target.py +2 -6
  180. ansible_test/_internal/thread.py +1 -4
  181. ansible_test/_internal/util.py +9 -14
  182. ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +14 -19
  183. ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +40 -27
  184. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -18
  185. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -2
  186. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +59 -71
  187. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -2
  188. ansible_test/_util/target/cli/ansible_test_cli_stub.py +4 -2
  189. ansible_test/_util/target/common/constants.py +2 -2
  190. ansible_test/_util/target/setup/bootstrap.sh +0 -6
  191. ansible/utils/py3compat.py +0 -27
  192. ansible_test/_data/pytest/config/legacy.ini +0 -4
  193. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/WHEEL +0 -0
  194. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/entry_points.txt +0 -0
  195. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/COPYING +0 -0
  196. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  197. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  198. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  199. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  200. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  201. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/top_level.txt +0 -0
ansible/playbook/block.py CHANGED
@@ -17,7 +17,6 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- import ansible.constants as C
21
20
  from ansible.errors import AnsibleParserError
22
21
  from ansible.module_utils.common.sentinel import Sentinel
23
22
  from ansible.playbook.attribute import NonInheritableFieldAttribute
@@ -27,7 +26,6 @@ from ansible.playbook.collectionsearch import CollectionSearch
27
26
  from ansible.playbook.delegatable import Delegatable
28
27
  from ansible.playbook.helpers import load_list_of_tasks
29
28
  from ansible.playbook.notifiable import Notifiable
30
- from ansible.playbook.role import Role
31
29
  from ansible.playbook.taggable import Taggable
32
30
 
33
31
 
@@ -220,65 +218,6 @@ class Block(Base, Conditional, CollectionSearch, Taggable, Notifiable, Delegatab
220
218
  new_me.validate()
221
219
  return new_me
222
220
 
223
- def serialize(self):
224
- """
225
- Override of the default serialize method, since when we're serializing
226
- a task we don't want to include the attribute list of tasks.
227
- """
228
-
229
- data = dict()
230
- for attr in self.fattributes:
231
- if attr not in ('block', 'rescue', 'always'):
232
- data[attr] = getattr(self, attr)
233
-
234
- data['dep_chain'] = self.get_dep_chain()
235
-
236
- if self._role is not None:
237
- data['role'] = self._role.serialize()
238
- if self._parent is not None:
239
- data['parent'] = self._parent.copy(exclude_tasks=True).serialize()
240
- data['parent_type'] = self._parent.__class__.__name__
241
-
242
- return data
243
-
244
- def deserialize(self, data):
245
- """
246
- Override of the default deserialize method, to match the above overridden
247
- serialize method
248
- """
249
-
250
- # import is here to avoid import loops
251
- from ansible.playbook.task_include import TaskInclude
252
- from ansible.playbook.handler_task_include import HandlerTaskInclude
253
-
254
- # we don't want the full set of attributes (the task lists), as that
255
- # would lead to a serialize/deserialize loop
256
- for attr in self.fattributes:
257
- if attr in data and attr not in ('block', 'rescue', 'always'):
258
- setattr(self, attr, data.get(attr))
259
-
260
- self._dep_chain = data.get('dep_chain', None)
261
-
262
- # if there was a serialized role, unpack it too
263
- role_data = data.get('role')
264
- if role_data:
265
- r = Role()
266
- r.deserialize(role_data)
267
- self._role = r
268
-
269
- parent_data = data.get('parent')
270
- if parent_data:
271
- parent_type = data.get('parent_type')
272
- if parent_type == 'Block':
273
- p = Block()
274
- elif parent_type == 'TaskInclude':
275
- p = TaskInclude()
276
- elif parent_type == 'HandlerTaskInclude':
277
- p = HandlerTaskInclude()
278
- p.deserialize(parent_data)
279
- self._parent = p
280
- self._dep_chain = self._parent.get_dep_chain()
281
-
282
221
  def set_loader(self, loader):
283
222
  self._loader = loader
284
223
  if self._parent:
@@ -376,8 +315,7 @@ class Block(Base, Conditional, CollectionSearch, Taggable, Notifiable, Delegatab
376
315
  filtered_block = evaluate_block(task)
377
316
  if filtered_block.has_tasks():
378
317
  tmp_list.append(filtered_block)
379
- elif ((task.action in C._ACTION_META and task.implicit) or
380
- task.evaluate_tags(self._play.only_tags, self._play.skip_tags, all_vars=all_vars)):
318
+ elif task.evaluate_tags(self._play.only_tags, self._play.skip_tags, all_vars=all_vars):
381
319
  tmp_list.append(task)
382
320
  return tmp_list
383
321
 
@@ -3,7 +3,6 @@
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
- from ansible.module_utils.six import string_types
7
6
  from ansible.playbook.attribute import FieldAttribute
8
7
  from ansible.utils.collection_loader import AnsibleCollectionConfig
9
8
  from ansible.utils.display import Display
@@ -32,7 +31,7 @@ def _ensure_default_collection(collection_list=None):
32
31
  class CollectionSearch:
33
32
 
34
33
  # this needs to be populated before we can resolve tasks/roles/etc
35
- collections = FieldAttribute(isa='list', listof=string_types, priority=100, default=_ensure_default_collection, always_post_validate=True, static=True)
34
+ collections = FieldAttribute(isa='list', listof=(str,), priority=100, default=_ensure_default_collection, always_post_validate=True, static=True)
36
35
 
37
36
  def _load_collections(self, attr, ds):
38
37
  # We are always a mixin with Base, so we can validate this untemplated
@@ -20,12 +20,11 @@ from __future__ import annotations
20
20
  from ansible.errors import AnsibleAssertionError
21
21
  from ansible.playbook.attribute import NonInheritableFieldAttribute
22
22
  from ansible.playbook.task import Task
23
- from ansible.module_utils.six import string_types
24
23
 
25
24
 
26
25
  class Handler(Task):
27
26
 
28
- listen = NonInheritableFieldAttribute(isa='list', default=list, listof=string_types, static=True)
27
+ listen = NonInheritableFieldAttribute(isa='list', default=list, listof=(str,), static=True)
29
28
 
30
29
  def __init__(self, block=None, role=None, task_include=None):
31
30
  self.notified_hosts = []
@@ -72,8 +71,3 @@ class Handler(Task):
72
71
 
73
72
  def is_host_notified(self, host):
74
73
  return host in self.notified_hosts
75
-
76
- def serialize(self):
77
- result = super(Handler, self).serialize()
78
- result['is_handler'] = True
79
- return result
@@ -165,17 +165,29 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
165
165
  subdir = 'tasks'
166
166
  if use_handlers:
167
167
  subdir = 'handlers'
168
+ try:
169
+ include_target = templar.template(task.args['_raw_params'])
170
+ except AnsibleUndefinedVariable as ex:
171
+ raise AnsibleParserError(
172
+ message=f"Error when evaluating variable in import path {task.args['_raw_params']!r}.",
173
+ help_text="When using static imports, ensure that any variables used in their names are defined in vars/vars_files\n"
174
+ "or extra-vars passed in from the command line. Static imports cannot use variables from facts or inventory\n"
175
+ "sources like group or host vars.",
176
+ obj=task_ds,
177
+ ) from ex
178
+ # FIXME this appears to be (almost?) duplicate code as in IncludedFile for include_tasks
168
179
  while parent_include is not None:
169
180
  if not isinstance(parent_include, TaskInclude):
170
181
  parent_include = parent_include._parent
171
182
  continue
172
- parent_include.post_validate(templar=templar)
173
- parent_include_dir = os.path.dirname(parent_include.args.get('_raw_params'))
183
+ if isinstance(parent_include, IncludeRole):
184
+ parent_include_dir = parent_include._role_path
185
+ else:
186
+ parent_include_dir = os.path.dirname(templar.template(parent_include.args.get('_raw_params')))
174
187
  if cumulative_path is None:
175
188
  cumulative_path = parent_include_dir
176
189
  elif not os.path.isabs(cumulative_path):
177
190
  cumulative_path = os.path.join(parent_include_dir, cumulative_path)
178
- include_target = templar.template(task.args['_raw_params'])
179
191
  if task._role:
180
192
  new_basedir = os.path.join(task._role._role_path, subdir, cumulative_path)
181
193
  include_file = loader.path_dwim_relative(new_basedir, subdir, include_target)
@@ -189,16 +201,6 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
189
201
  parent_include = parent_include._parent
190
202
 
191
203
  if not found:
192
- try:
193
- include_target = templar.template(task.args['_raw_params'])
194
- except AnsibleUndefinedVariable as ex:
195
- raise AnsibleParserError(
196
- message=f"Error when evaluating variable in import path {task.args['_raw_params']!r}.",
197
- help_text="When using static imports, ensure that any variables used in their names are defined in vars/vars_files\n"
198
- "or extra-vars passed in from the command line. Static imports cannot use variables from facts or inventory\n"
199
- "sources like group or host vars.",
200
- obj=task_ds,
201
- ) from ex
202
204
  if task._role:
203
205
  include_file = loader.path_dwim_relative(task._role._role_path, subdir, include_target)
204
206
  else:
@@ -230,13 +232,6 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
230
232
  variable_manager=variable_manager,
231
233
  )
232
234
 
233
- tags = ti_copy.tags[:]
234
-
235
- # now we extend the tags on each of the included blocks
236
- for b in included_blocks:
237
- b.tags = list(set(b.tags).union(tags))
238
- # FIXME - END
239
-
240
235
  # FIXME: handlers shouldn't need this special handling, but do
241
236
  # right now because they don't iterate blocks correctly
242
237
  if use_handlers:
@@ -203,7 +203,7 @@ class IncludedFile:
203
203
  for from_arg in new_task.FROM_ARGS:
204
204
  if from_arg in include_args:
205
205
  from_key = from_arg.removesuffix('_from')
206
- new_task._from_files[from_key] = include_args.pop(from_arg)
206
+ new_task._from_files[from_key] = include_args.get(from_arg)
207
207
 
208
208
  inc_file = IncludedFile(role_name, include_args, special_vars, new_task, is_role=True)
209
209
 
ansible/playbook/play.py CHANGED
@@ -17,12 +17,15 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
+ import functools as _functools
21
+ import pathlib as _pathlib
22
+
20
23
  from ansible import constants as C
21
24
  from ansible import context
22
25
  from ansible.errors import AnsibleError
23
- from ansible.errors import AnsibleParserError, AnsibleAssertionError
26
+ from ansible.errors import AnsibleParserError, AnsibleAssertionError, AnsibleValueOmittedError
24
27
  from ansible.module_utils.common.collections import is_sequence
25
- from ansible.module_utils.six import binary_type, string_types, text_type
28
+ from ansible.module_utils.common.yaml import yaml_dump
26
29
  from ansible.playbook.attribute import NonInheritableFieldAttribute
27
30
  from ansible.playbook.base import Base
28
31
  from ansible.playbook.block import Block
@@ -34,6 +37,8 @@ from ansible.playbook.taggable import Taggable
34
37
  from ansible.parsing.vault import EncryptedString
35
38
  from ansible.utils.display import Display
36
39
 
40
+ from ansible._internal._templating._engine import TemplateEngine as _TE
41
+
37
42
  display = Display()
38
43
 
39
44
 
@@ -53,11 +58,11 @@ class Play(Base, Taggable, CollectionSearch):
53
58
  """
54
59
 
55
60
  # =================================================================================
56
- hosts = NonInheritableFieldAttribute(isa='list', required=True, listof=string_types, always_post_validate=True, priority=-2)
61
+ hosts = NonInheritableFieldAttribute(isa='list', required=True, listof=(str,), always_post_validate=True, priority=-2)
57
62
 
58
63
  # Facts
59
64
  gather_facts = NonInheritableFieldAttribute(isa='bool', default=None, always_post_validate=True)
60
- gather_subset = NonInheritableFieldAttribute(isa='list', default=None, listof=string_types, always_post_validate=True)
65
+ gather_subset = NonInheritableFieldAttribute(isa='list', default=None, listof=(str,), always_post_validate=True)
61
66
  gather_timeout = NonInheritableFieldAttribute(isa='int', default=None, always_post_validate=True)
62
67
  fact_path = NonInheritableFieldAttribute(isa='string', default=None)
63
68
 
@@ -65,6 +70,8 @@ class Play(Base, Taggable, CollectionSearch):
65
70
  vars_files = NonInheritableFieldAttribute(isa='list', default=list, priority=99)
66
71
  vars_prompt = NonInheritableFieldAttribute(isa='list', default=list, always_post_validate=False)
67
72
 
73
+ validate_argspec = NonInheritableFieldAttribute(isa='string', always_post_validate=True)
74
+
68
75
  # Role Attributes
69
76
  roles = NonInheritableFieldAttribute(isa='list', default=list, priority=90)
70
77
 
@@ -120,10 +127,10 @@ class Play(Base, Taggable, CollectionSearch):
120
127
  for entry in value:
121
128
  if entry is None:
122
129
  raise AnsibleParserError("Hosts list cannot contain values of 'None'. Please check your playbook")
123
- elif not isinstance(entry, (binary_type, text_type)):
130
+ elif not isinstance(entry, (bytes, str)):
124
131
  raise AnsibleParserError("Hosts list contains an invalid host value: '{host!s}'".format(host=entry))
125
132
 
126
- elif not isinstance(value, (binary_type, text_type, EncryptedString)):
133
+ elif not isinstance(value, (bytes, str, EncryptedString)):
127
134
  raise AnsibleParserError("Hosts list must be a sequence or string. Please check your playbook.")
128
135
 
129
136
  def get_name(self):
@@ -303,23 +310,13 @@ class Play(Base, Taggable, CollectionSearch):
303
310
 
304
311
  t = Task(block=flush_block)
305
312
  t.action = 'meta'
306
- t.resolved_action = 'ansible.builtin.meta'
313
+ t._resolved_action = 'ansible.builtin.meta'
307
314
  t.args['_raw_params'] = 'flush_handlers'
308
315
  t.implicit = True
309
316
  t.set_loader(self._loader)
317
+ t.tags = ['always']
310
318
 
311
- if self.tags:
312
- # Avoid calling flush_handlers in case the whole play is skipped on tags,
313
- # this could be performance improvement since calling flush_handlers on
314
- # large inventories could be expensive even if no hosts are notified
315
- # since we call flush_handlers per host.
316
- # Block.filter_tagged_tasks ignores evaluating tags on implicit meta
317
- # tasks so we need to explicitly call Task.evaluate_tags here.
318
- t.tags = self.tags
319
- if t.evaluate_tags(self.only_tags, self.skip_tags, all_vars=self.vars):
320
- flush_block.block = [t]
321
- else:
322
- flush_block.block = [t]
319
+ flush_block.block = [t]
323
320
 
324
321
  # NOTE keep flush_handlers tasks even if a section has no regular tasks,
325
322
  # there may be notified handlers from the previous section
@@ -400,36 +397,6 @@ class Play(Base, Taggable, CollectionSearch):
400
397
  tasklist.append(task)
401
398
  return tasklist
402
399
 
403
- def serialize(self):
404
- data = super(Play, self).serialize()
405
-
406
- roles = []
407
- for role in self.get_roles():
408
- roles.append(role.serialize())
409
- data['roles'] = roles
410
- data['included_path'] = self._included_path
411
- data['action_groups'] = self._action_groups
412
- data['group_actions'] = self._group_actions
413
-
414
- return data
415
-
416
- def deserialize(self, data):
417
- super(Play, self).deserialize(data)
418
-
419
- self._included_path = data.get('included_path', None)
420
- self._action_groups = data.get('action_groups', {})
421
- self._group_actions = data.get('group_actions', {})
422
- if 'roles' in data:
423
- role_data = data.get('roles', [])
424
- roles = []
425
- for role in role_data:
426
- r = Role()
427
- r.deserialize(role)
428
- roles.append(r)
429
-
430
- setattr(self, 'roles', roles)
431
- del data['roles']
432
-
433
400
  def copy(self):
434
401
  new_me = super(Play, self).copy()
435
402
  new_me.role_cache = self.role_cache.copy()
@@ -438,3 +405,92 @@ class Play(Base, Taggable, CollectionSearch):
438
405
  new_me._action_groups = self._action_groups
439
406
  new_me._group_actions = self._group_actions
440
407
  return new_me
408
+
409
+ def _post_validate_validate_argspec(self, attr: NonInheritableFieldAttribute, value: object, templar: _TE) -> str | None:
410
+ """Validate user input is a bool or string, and return the corresponding argument spec name."""
411
+
412
+ # Ensure the configuration is valid
413
+ if isinstance(value, str):
414
+ try:
415
+ value = templar.template(value)
416
+ except AnsibleValueOmittedError:
417
+ value = False
418
+
419
+ if not isinstance(value, (str, bool)):
420
+ raise AnsibleParserError(f"validate_argspec must be a boolean or string, not {type(value)}", obj=value)
421
+
422
+ # Short-circuit if configuration is turned off or inapplicable
423
+ if not value or self._origin is None:
424
+ return None
425
+
426
+ # Use the requested argument spec or fall back to the play name
427
+ argspec_name = None
428
+ if isinstance(value, str):
429
+ argspec_name = value
430
+ elif self._ds.get("name"):
431
+ argspec_name = self.name
432
+
433
+ metadata_err = argspec_err = ""
434
+ if not argspec_name:
435
+ argspec_err = (
436
+ "A play name is required when validate_argspec is True. "
437
+ "Alternatively, set validate_argspec to the name of an argument spec."
438
+ )
439
+ if self._metadata_path is None:
440
+ metadata_err = "A playbook meta file is required. Considered:\n - "
441
+ metadata_err += "\n - ".join([path.as_posix() for path in self._metadata_candidate_paths])
442
+
443
+ if metadata_err or argspec_err:
444
+ error = f"{argspec_err + (' ' if argspec_err else '')}{metadata_err}"
445
+ raise AnsibleParserError(error, obj=self._origin)
446
+
447
+ metadata = self._loader.load_from_file(self._metadata_path)
448
+
449
+ try:
450
+ metadata = metadata['argument_specs']
451
+ metadata = metadata[argspec_name]
452
+ options = metadata['options']
453
+ except (TypeError, KeyError):
454
+ options = None
455
+
456
+ if not isinstance(options, dict):
457
+ raise AnsibleParserError(
458
+ f"No argument spec named '{argspec_name}' in {self._metadata_path}. Minimally expected:\n"
459
+ + yaml_dump({"argument_specs": {f"{argspec_name!s}": {"options": {}}}}),
460
+ obj=metadata,
461
+ )
462
+
463
+ return argspec_name
464
+
465
+ @property
466
+ def _metadata_candidate_paths(self) -> list[_pathlib.Path]:
467
+ """A list of possible playbook.meta paths in configured order."""
468
+ extensions = C.config.get_config_value("YAML_FILENAME_EXTENSIONS")
469
+ if self._origin.path.endswith(tuple(extensions)):
470
+ playbook_without_ext = self._origin.path.rsplit('.', 1)[0]
471
+ else:
472
+ playbook_without_ext = self._origin.path
473
+
474
+ return [_pathlib.Path(playbook_without_ext + ".meta" + ext) for ext in extensions + ['']]
475
+
476
+ @_functools.cached_property
477
+ def _metadata_path(self) -> str | None:
478
+ """Locate playbook meta path:
479
+
480
+ playbook{ext?} -> playbook.meta{ext?}
481
+ """
482
+ if self._origin is None:
483
+ # adhoc, ansible-console don't have an associated playbook
484
+ return None
485
+ for candidate in self._metadata_candidate_paths:
486
+ if candidate.is_file():
487
+ return candidate.as_posix()
488
+ return None
489
+
490
+ @property
491
+ def argument_spec(self) -> dict:
492
+ """Retrieve the argument spec if one is configured."""
493
+ if not self.validate_argspec:
494
+ return {}
495
+
496
+ return self._loader.load_from_file(self._metadata_path)['argument_specs'][self.validate_argspec]['options']
@@ -325,3 +325,7 @@ class PlayContext(Base):
325
325
  variables[var_opt] = var_val
326
326
  except AttributeError:
327
327
  continue
328
+
329
+ def deserialize(self, data):
330
+ """Do not use this method. Backward compatibility for network connections plugins that rely on it."""
331
+ self.from_attrs(data)
@@ -27,7 +27,6 @@ from ansible import constants as C
27
27
  from ansible.errors import AnsibleError, AnsibleParserError, AnsibleAssertionError
28
28
  from ansible.module_utils.common.sentinel import Sentinel
29
29
  from ansible.module_utils.common.text.converters import to_text
30
- from ansible.module_utils.six import binary_type, text_type
31
30
  from ansible.playbook.base import Base
32
31
  from ansible.playbook.collectionsearch import CollectionSearch
33
32
  from ansible.playbook.conditional import Conditional
@@ -37,6 +36,7 @@ from ansible.playbook.role.metadata import RoleMetadata
37
36
  from ansible.playbook.taggable import Taggable
38
37
  from ansible.plugins.loader import add_all_plugin_dirs
39
38
  from ansible.utils.collection_loader import AnsibleCollectionConfig
39
+ from ansible.utils.display import Display
40
40
  from ansible.utils.path import is_subpath
41
41
  from ansible.utils.vars import combine_vars
42
42
 
@@ -52,14 +52,12 @@ if _t.TYPE_CHECKING:
52
52
 
53
53
  __all__ = ['Role', 'hash_params']
54
54
 
55
- # TODO: this should be a utility function, but can't be a member of
56
- # the role due to the fact that it would require the use of self
57
- # in a static method. This is also used in the base class for
58
- # strategies (ansible/plugins/strategy/__init__.py)
55
+ _display = Display()
59
56
 
60
57
 
61
58
  def hash_params(params):
62
59
  """
60
+ DEPRECATED
63
61
  Construct a data structure of parameters that is hashable.
64
62
 
65
63
  This requires changing any mutable data structures into immutable ones.
@@ -71,10 +69,16 @@ def hash_params(params):
71
69
  1) There shouldn't be any unhashable scalars specified in the yaml
72
70
  2) Our only choice would be to return an error anyway.
73
71
  """
72
+
73
+ _display.deprecated(
74
+ msg="The hash_params function is deprecated as its consumers have moved to internal alternatives",
75
+ version='2.24',
76
+ help_text='Contact the plugin author to update their code',
77
+ )
74
78
  # Any container is unhashable if it contains unhashable items (for
75
79
  # instance, tuple() is a Hashable subclass but if it contains a dict, it
76
80
  # cannot be hashed)
77
- if isinstance(params, Container) and not isinstance(params, (text_type, binary_type)):
81
+ if isinstance(params, Container) and not isinstance(params, (str, bytes)):
78
82
  if isinstance(params, Mapping):
79
83
  try:
80
84
  # Optimistically hope the contents are all hashable
@@ -651,65 +655,6 @@ class Role(Base, Conditional, Taggable, CollectionSearch, Delegatable):
651
655
 
652
656
  return block_list
653
657
 
654
- def serialize(self, include_deps=True):
655
- res = super(Role, self).serialize()
656
-
657
- res['_role_name'] = self._role_name
658
- res['_role_path'] = self._role_path
659
- res['_role_vars'] = self._role_vars
660
- res['_role_params'] = self._role_params
661
- res['_default_vars'] = self._default_vars
662
- res['_had_task_run'] = self._had_task_run.copy()
663
- res['_completed'] = self._completed.copy()
664
-
665
- res['_metadata'] = self._metadata.serialize()
666
-
667
- if include_deps:
668
- deps = []
669
- for role in self.get_direct_dependencies():
670
- deps.append(role.serialize())
671
- res['_dependencies'] = deps
672
-
673
- parents = []
674
- for parent in self._parents:
675
- parents.append(parent.serialize(include_deps=False))
676
- res['_parents'] = parents
677
-
678
- return res
679
-
680
- def deserialize(self, data, include_deps=True):
681
- self._role_name = data.get('_role_name', '')
682
- self._role_path = data.get('_role_path', '')
683
- self._role_vars = data.get('_role_vars', dict())
684
- self._role_params = data.get('_role_params', dict())
685
- self._default_vars = data.get('_default_vars', dict())
686
- self._had_task_run = data.get('_had_task_run', dict())
687
- self._completed = data.get('_completed', dict())
688
-
689
- if include_deps:
690
- deps = []
691
- for dep in data.get('_dependencies', []):
692
- r = Role()
693
- r.deserialize(dep)
694
- deps.append(r)
695
- setattr(self, '_dependencies', deps)
696
-
697
- parent_data = data.get('_parents', [])
698
- parents = []
699
- for parent in parent_data:
700
- r = Role()
701
- r.deserialize(parent, include_deps=False)
702
- parents.append(r)
703
- setattr(self, '_parents', parents)
704
-
705
- metadata_data = data.get('_metadata')
706
- if metadata_data:
707
- m = RoleMetadata()
708
- m.deserialize(metadata_data)
709
- self._metadata = m
710
-
711
- super(Role, self).deserialize(data)
712
-
713
658
  def set_loader(self, loader):
714
659
  self._loader = loader
715
660
  for parent in self._parents:
@@ -22,7 +22,6 @@ import os
22
22
  from ansible import constants as C
23
23
  from ansible.errors import AnsibleError, AnsibleAssertionError
24
24
  from ansible.module_utils._internal._datatag import AnsibleTagHelper
25
- from ansible.module_utils.six import string_types
26
25
  from ansible.playbook.attribute import NonInheritableFieldAttribute
27
26
  from ansible.playbook.base import Base
28
27
  from ansible.playbook.collectionsearch import CollectionSearch
@@ -70,7 +69,7 @@ class RoleDefinition(Base, Conditional, Taggable, CollectionSearch):
70
69
  if isinstance(ds, int):
71
70
  ds = "%s" % ds
72
71
 
73
- if not isinstance(ds, dict) and not isinstance(ds, string_types):
72
+ if not isinstance(ds, dict) and not isinstance(ds, str):
74
73
  raise AnsibleAssertionError()
75
74
 
76
75
  if isinstance(ds, dict):
@@ -113,11 +112,11 @@ class RoleDefinition(Base, Conditional, Taggable, CollectionSearch):
113
112
  string), just that string
114
113
  """
115
114
 
116
- if isinstance(ds, string_types):
115
+ if isinstance(ds, str):
117
116
  return ds
118
117
 
119
118
  role_name = ds.get('role', ds.get('name'))
120
- if not role_name or not isinstance(role_name, string_types):
119
+ if not role_name or not isinstance(role_name, str):
121
120
  raise AnsibleError('role definitions must contain a role name', obj=ds)
122
121
 
123
122
  # if we have the required datastructures, and if the role_name
@@ -18,7 +18,6 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  from ansible.errors import AnsibleError, AnsibleParserError
21
- from ansible.module_utils.six import string_types
22
21
  from ansible.playbook.delegatable import Delegatable
23
22
  from ansible.playbook.role.definition import RoleDefinition
24
23
 
@@ -40,10 +39,10 @@ class RoleInclude(RoleDefinition, Delegatable):
40
39
  @staticmethod
41
40
  def load(data, play, current_role_path=None, parent_role=None, variable_manager=None, loader=None, collection_list=None):
42
41
 
43
- if not (isinstance(data, string_types) or isinstance(data, dict)):
42
+ if not (isinstance(data, str) or isinstance(data, dict)):
44
43
  raise AnsibleParserError("Invalid role definition.", obj=data)
45
44
 
46
- if isinstance(data, string_types) and ',' in data:
45
+ if isinstance(data, str) and ',' in data:
47
46
  raise AnsibleError("Invalid old style role requirement: %s" % data)
48
47
 
49
48
  ri = RoleInclude(play=play, role_basedir=current_role_path, variable_manager=variable_manager, loader=loader, collection_list=collection_list)
@@ -20,7 +20,6 @@ from __future__ import annotations
20
20
  import os
21
21
 
22
22
  from ansible.errors import AnsibleParserError, AnsibleError
23
- from ansible.module_utils.six import string_types
24
23
  from ansible.playbook.attribute import NonInheritableFieldAttribute
25
24
  from ansible.playbook.base import Base
26
25
  from ansible.playbook.collectionsearch import CollectionSearch
@@ -70,7 +69,7 @@ class RoleMetadata(Base, CollectionSearch):
70
69
 
71
70
  for role_def in ds:
72
71
  # FIXME: consolidate with ansible-galaxy to keep this in sync
73
- if isinstance(role_def, string_types) or 'role' in role_def or 'name' in role_def:
72
+ if isinstance(role_def, str) or 'role' in role_def or 'name' in role_def:
74
73
  roles.append(role_def)
75
74
  continue
76
75
  try:
@@ -106,13 +105,3 @@ class RoleMetadata(Base, CollectionSearch):
106
105
  collection_search_list=collection_search_list)
107
106
  except AssertionError as ex:
108
107
  raise AnsibleParserError("A malformed list of role dependencies was encountered.", obj=self._ds) from ex
109
-
110
- def serialize(self):
111
- return dict(
112
- allow_duplicates=self._allow_duplicates,
113
- dependencies=self._dependencies
114
- )
115
-
116
- def deserialize(self, data):
117
- setattr(self, 'allow_duplicates', data.get('allow_duplicates', False))
118
- setattr(self, 'dependencies', data.get('dependencies', []))
@@ -18,7 +18,6 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  from ansible.errors import AnsibleError
21
- from ansible.module_utils.six import string_types
22
21
  from ansible.playbook.role.definition import RoleDefinition
23
22
  from ansible.utils.display import Display
24
23
  from ansible.utils.galaxy import scm_archive_resource
@@ -65,7 +64,7 @@ class RoleRequirement(RoleDefinition):
65
64
  @staticmethod
66
65
  def role_yaml_parse(role):
67
66
 
68
- if isinstance(role, string_types):
67
+ if isinstance(role, str):
69
68
  name = None
70
69
  scm = None
71
70
  src = None
@@ -23,7 +23,6 @@ from ansible.playbook.task_include import TaskInclude
23
23
  from ansible.playbook.role import Role
24
24
  from ansible.playbook.role.include import RoleInclude
25
25
  from ansible.utils.display import Display
26
- from ansible.module_utils.six import string_types
27
26
  from ansible._internal._templating._engine import TemplateEngine
28
27
 
29
28
  __all__ = ['IncludeRole']
@@ -137,7 +136,7 @@ class IncludeRole(TaskInclude):
137
136
  for key in my_arg_names.intersection(IncludeRole.FROM_ARGS):
138
137
  from_key = key.removesuffix('_from')
139
138
  args_value = ir.args.get(key)
140
- if not isinstance(args_value, string_types):
139
+ if not isinstance(args_value, str):
141
140
  raise AnsibleParserError('Expected a string for %s but got %s instead' % (key, type(args_value)))
142
141
  ir._from_files[from_key] = args_value
143
142