ansible-core 2.19.4rc1__py3-none-any.whl → 2.20.0__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 (215) 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 +67 -67
  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/config/base.yml +1 -25
  19. ansible/config/manager.py +0 -2
  20. ansible/executor/play_iterator.py +42 -20
  21. ansible/executor/playbook_executor.py +0 -9
  22. ansible/executor/task_executor.py +26 -18
  23. ansible/executor/task_queue_manager.py +1 -3
  24. ansible/galaxy/api.py +33 -80
  25. ansible/galaxy/collection/__init__.py +11 -21
  26. ansible/galaxy/dependency_resolution/__init__.py +10 -9
  27. ansible/galaxy/dependency_resolution/dataclasses.py +86 -70
  28. ansible/galaxy/dependency_resolution/providers.py +54 -134
  29. ansible/galaxy/dependency_resolution/versioning.py +2 -4
  30. ansible/galaxy/role.py +1 -33
  31. ansible/inventory/manager.py +2 -3
  32. ansible/keyword_desc.yml +0 -3
  33. ansible/module_utils/_internal/_datatag/__init__.py +2 -10
  34. ansible/module_utils/_internal/_no_six.py +86 -0
  35. ansible/module_utils/_text.py +28 -8
  36. ansible/module_utils/ansible_release.py +2 -2
  37. ansible/module_utils/basic.py +26 -23
  38. ansible/module_utils/common/_collections_compat.py +11 -2
  39. ansible/module_utils/common/collections.py +8 -3
  40. ansible/module_utils/common/dict_transformations.py +1 -2
  41. ansible/module_utils/common/network.py +4 -2
  42. ansible/module_utils/common/parameters.py +32 -41
  43. ansible/module_utils/common/text/converters.py +109 -23
  44. ansible/module_utils/common/text/formatters.py +6 -2
  45. ansible/module_utils/common/validation.py +11 -9
  46. ansible/module_utils/connection.py +8 -3
  47. ansible/module_utils/facts/hardware/linux.py +23 -7
  48. ansible/module_utils/facts/hardware/netbsd.py +1 -1
  49. ansible/module_utils/facts/hardware/sunos.py +2 -1
  50. ansible/module_utils/facts/packages.py +6 -2
  51. ansible/module_utils/facts/system/distribution.py +2 -1
  52. ansible/module_utils/facts/system/env.py +6 -3
  53. ansible/module_utils/facts/system/local.py +3 -1
  54. ansible/module_utils/parsing/convert_bool.py +6 -2
  55. ansible/module_utils/service.py +2 -3
  56. ansible/module_utils/six/__init__.py +11 -6
  57. ansible/module_utils/yumdnf.py +0 -5
  58. ansible/modules/apt.py +18 -13
  59. ansible/modules/apt_repository.py +1 -1
  60. ansible/modules/assemble.py +5 -9
  61. ansible/modules/blockinfile.py +39 -23
  62. ansible/modules/cron.py +26 -35
  63. ansible/modules/deb822_repository.py +83 -12
  64. ansible/modules/dnf.py +3 -7
  65. ansible/modules/dnf5.py +4 -6
  66. ansible/modules/expect.py +0 -3
  67. ansible/modules/find.py +1 -2
  68. ansible/modules/get_url.py +1 -1
  69. ansible/modules/git.py +4 -5
  70. ansible/modules/include_vars.py +1 -1
  71. ansible/modules/known_hosts.py +7 -1
  72. ansible/modules/lineinfile.py +71 -63
  73. ansible/modules/package_facts.py +1 -1
  74. ansible/modules/pip.py +8 -2
  75. ansible/modules/replace.py +6 -6
  76. ansible/modules/service.py +3 -4
  77. ansible/modules/stat.py +20 -0
  78. ansible/modules/uri.py +9 -10
  79. ansible/modules/user.py +1 -2
  80. ansible/modules/wait_for.py +2 -2
  81. ansible/modules/wait_for_connection.py +2 -1
  82. ansible/modules/yum_repository.py +1 -16
  83. ansible/parsing/dataloader.py +24 -31
  84. ansible/parsing/vault/__init__.py +1 -2
  85. ansible/playbook/base.py +8 -56
  86. ansible/playbook/block.py +0 -60
  87. ansible/playbook/collectionsearch.py +1 -2
  88. ansible/playbook/handler.py +1 -7
  89. ansible/playbook/helpers.py +0 -7
  90. ansible/playbook/included_file.py +1 -1
  91. ansible/playbook/play.py +102 -36
  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 +11 -50
  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 +5 -2
  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.4rc1.dist-info → ansible_core-2.20.0.dist-info}/METADATA +4 -4
  153. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/RECORD +213 -214
  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/ansible.txt +1 -1
  159. ansible_test/_data/requirements/sanity.ansible-doc.txt +2 -2
  160. ansible_test/_data/requirements/sanity.changelog.txt +2 -2
  161. ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
  162. ansible_test/_data/requirements/sanity.import.txt +1 -1
  163. ansible_test/_data/requirements/sanity.integration-aliases.txt +1 -1
  164. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  165. ansible_test/_data/requirements/sanity.pylint.txt +6 -6
  166. ansible_test/_data/requirements/sanity.runtime-metadata.txt +1 -1
  167. ansible_test/_data/requirements/sanity.validate-modules.txt +2 -2
  168. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  169. ansible_test/_internal/cache.py +2 -5
  170. ansible_test/_internal/cli/compat.py +1 -1
  171. ansible_test/_internal/commands/coverage/combine.py +1 -3
  172. ansible_test/_internal/commands/integration/__init__.py +3 -7
  173. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  174. ansible_test/_internal/commands/integration/coverage.py +1 -3
  175. ansible_test/_internal/commands/integration/filters.py +5 -10
  176. ansible_test/_internal/commands/sanity/pylint.py +11 -0
  177. ansible_test/_internal/commands/sanity/validate_modules.py +1 -5
  178. ansible_test/_internal/commands/units/__init__.py +1 -13
  179. ansible_test/_internal/compat/packaging.py +2 -2
  180. ansible_test/_internal/compat/yaml.py +2 -2
  181. ansible_test/_internal/completion.py +2 -5
  182. ansible_test/_internal/config.py +2 -7
  183. ansible_test/_internal/coverage_util.py +1 -1
  184. ansible_test/_internal/delegation.py +2 -0
  185. ansible_test/_internal/docker_util.py +1 -1
  186. ansible_test/_internal/host_profiles.py +6 -11
  187. ansible_test/_internal/provider/__init__.py +2 -5
  188. ansible_test/_internal/provisioning.py +2 -5
  189. ansible_test/_internal/pypi_proxy.py +1 -1
  190. ansible_test/_internal/python_requirements.py +1 -1
  191. ansible_test/_internal/target.py +2 -6
  192. ansible_test/_internal/thread.py +1 -4
  193. ansible_test/_internal/util.py +9 -14
  194. ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +14 -19
  195. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +48 -45
  196. ansible_test/_util/controller/sanity/pylint/plugins/string_format.py +9 -7
  197. ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +51 -37
  198. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -18
  199. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -2
  200. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +59 -71
  201. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -2
  202. ansible_test/_util/target/cli/ansible_test_cli_stub.py +4 -2
  203. ansible_test/_util/target/common/constants.py +2 -2
  204. ansible_test/_util/target/setup/bootstrap.sh +0 -6
  205. ansible/utils/py3compat.py +0 -27
  206. ansible_test/_data/pytest/config/legacy.ini +0 -4
  207. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/WHEEL +0 -0
  208. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/entry_points.txt +0 -0
  209. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/COPYING +0 -0
  210. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  211. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  212. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  213. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  214. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  215. {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/top_level.txt +0 -0
@@ -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
 
@@ -19,11 +19,14 @@ from __future__ import annotations
19
19
 
20
20
  import typing as t
21
21
 
22
+ from ansible._internal._templating._engine import TemplateEngine
22
23
  from ansible.errors import AnsibleError
23
24
  from ansible.module_utils.common.sentinel import Sentinel
24
25
  from ansible.module_utils._internal._datatag import AnsibleTagHelper
25
26
  from ansible.playbook.attribute import FieldAttribute
26
- from ansible._internal._templating._engine import TemplateEngine
27
+ from ansible.utils.display import Display
28
+
29
+ _display = Display()
27
30
 
28
31
 
29
32
  def _flatten_tags(tags: list[str | int]) -> list[str | int]:
@@ -38,17 +41,25 @@ def _flatten_tags(tags: list[str | int]) -> list[str | int]:
38
41
 
39
42
  class Taggable:
40
43
 
44
+ _RESERVED = frozenset(['tagged', 'all', 'untagged'])
41
45
  untagged = frozenset(['untagged'])
42
46
  tags = FieldAttribute(isa='list', default=list, listof=(str, int), extend=True)
43
47
 
44
48
  def _load_tags(self, attr, ds):
49
+
50
+ tags = None
45
51
  if isinstance(ds, list):
46
- return ds
52
+ tags = ds
53
+ elif isinstance(ds, str):
54
+ tags = [AnsibleTagHelper.tag_copy(ds, item.strip()) for item in ds.split(',')]
55
+
56
+ if tags is None:
57
+ raise AnsibleError('tags must be specified as a list', obj=ds)
47
58
 
48
- if isinstance(ds, str):
49
- return [AnsibleTagHelper.tag_copy(ds, item.strip()) for item in ds.split(',')]
59
+ if found := self._RESERVED.intersection(tags):
60
+ _display.warning(f"Found reserved tagnames in tags: {list(found)!r}, we do not recommend doing this as it might give unexpected results", obj=ds)
50
61
 
51
- raise AnsibleError('tags must be specified as a list', obj=ds)
62
+ return tags
52
63
 
53
64
  def _get_all_taggable_objects(self) -> t.Iterable[Taggable]:
54
65
  obj = self
ansible/playbook/task.py CHANGED
@@ -25,7 +25,6 @@ from ansible.errors import AnsibleError, AnsibleParserError, AnsibleUndefinedVar
25
25
  from ansible.executor.module_common import _get_action_arg_defaults
26
26
  from ansible.module_utils.common.text.converters import to_native
27
27
  from ansible.module_utils._internal._datatag import AnsibleTagHelper
28
- from ansible.module_utils.six import string_types
29
28
  from ansible.parsing.mod_args import ModuleArgsParser, RAW_PARAM_MODULES
30
29
  from ansible.plugins.action import ActionBase
31
30
  from ansible.plugins.loader import action_loader, module_loader, lookup_loader
@@ -37,7 +36,6 @@ from ansible.playbook.conditional import Conditional
37
36
  from ansible.playbook.delegatable import Delegatable
38
37
  from ansible.playbook.loop_control import LoopControl
39
38
  from ansible.playbook.notifiable import Notifiable
40
- from ansible.playbook.role import Role
41
39
  from ansible.playbook.taggable import Taggable
42
40
  from ansible._internal import _task
43
41
  from ansible._internal._templating import _marker_behaviors
@@ -161,7 +159,7 @@ class Task(Base, Conditional, Taggable, CollectionSearch, Notifiable, Delegatabl
161
159
  def _merge_kv(self, ds):
162
160
  if ds is None:
163
161
  return ""
164
- elif isinstance(ds, string_types):
162
+ elif isinstance(ds, str):
165
163
  return ds
166
164
  elif isinstance(ds, dict):
167
165
  buf = ""
@@ -505,53 +503,6 @@ class Task(Base, Conditional, Taggable, CollectionSearch, Notifiable, Delegatabl
505
503
 
506
504
  return new_me
507
505
 
508
- def serialize(self):
509
- data = super(Task, self).serialize()
510
-
511
- if not self._squashed and not self._finalized:
512
- if self._parent:
513
- data['parent'] = self._parent.serialize()
514
- data['parent_type'] = self._parent.__class__.__name__
515
-
516
- if self._role:
517
- data['role'] = self._role.serialize()
518
-
519
- data['implicit'] = self.implicit
520
- data['_resolved_action'] = self._resolved_action
521
-
522
- return data
523
-
524
- def deserialize(self, data):
525
-
526
- # import is here to avoid import loops
527
- from ansible.playbook.task_include import TaskInclude
528
- from ansible.playbook.handler_task_include import HandlerTaskInclude
529
-
530
- parent_data = data.get('parent', None)
531
- if parent_data:
532
- parent_type = data.get('parent_type')
533
- if parent_type == 'Block':
534
- p = Block()
535
- elif parent_type == 'TaskInclude':
536
- p = TaskInclude()
537
- elif parent_type == 'HandlerTaskInclude':
538
- p = HandlerTaskInclude()
539
- p.deserialize(parent_data)
540
- self._parent = p
541
- del data['parent']
542
-
543
- role_data = data.get('role')
544
- if role_data:
545
- r = Role()
546
- r.deserialize(role_data)
547
- self._role = r
548
- del data['role']
549
-
550
- self.implicit = data.get('implicit', False)
551
- self._resolved_action = data.get('_resolved_action')
552
-
553
- super(Task, self).deserialize(data)
554
-
555
506
  def set_loader(self, loader):
556
507
  """
557
508
  Sets the loader on this object and recursively on parent, child objects.
@@ -629,6 +580,16 @@ class Task(Base, Conditional, Taggable, CollectionSearch, Notifiable, Delegatabl
629
580
  attrs.update(_resolved_action=self._resolved_action)
630
581
  return attrs
631
582
 
583
+ def from_attrs(self, attrs):
584
+ super().from_attrs(attrs)
585
+
586
+ # from_attrs is only used to create a finalized task
587
+ # from attrs from the Worker/TaskExecutor
588
+ # Those attrs are finalized and squashed in the TE
589
+ # and controller side use needs to reflect that
590
+ self._finalized = True
591
+ self._squashed = True
592
+
632
593
  def _resolve_conditional(
633
594
  self,
634
595
  conditional: list[str | bool],
@@ -29,7 +29,6 @@ from ansible.module_utils.common.arg_spec import ArgumentSpecValidator
29
29
  from ansible.module_utils.errors import UnsupportedError
30
30
  from ansible.module_utils.json_utils import _filter_non_json_lines
31
31
  from ansible.module_utils.common.json import Direction, get_module_encoder, get_module_decoder
32
- from ansible.module_utils.six import binary_type, string_types, text_type
33
32
  from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
34
33
  from ansible.release import __version__
35
34
  from ansible.utils.collection_loader import resource_from_fqcr
@@ -52,7 +51,7 @@ if t.TYPE_CHECKING:
52
51
 
53
52
 
54
53
  def _validate_utf8_json(d):
55
- if isinstance(d, text_type):
54
+ if isinstance(d, str):
56
55
  # Purposefully not using to_bytes here for performance reasons
57
56
  d.encode(encoding='utf-8', errors='strict')
58
57
  elif isinstance(d, dict):
@@ -288,14 +287,6 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
288
287
  elif leaf_module_name == 'async_status' and collection_name in rewrite_collection_names:
289
288
  module_name = '%s.%s' % (win_collection, leaf_module_name)
290
289
 
291
- # TODO: move this tweak down to the modules, not extensible here
292
- # Remove extra quotes surrounding path parameters before sending to module.
293
- if leaf_module_name in ['win_stat', 'win_file', 'win_copy', 'slurp'] and module_args and \
294
- hasattr(self._connection._shell, '_unquote'):
295
- for key in ('src', 'dest', 'path'):
296
- if key in module_args:
297
- module_args[key] = self._connection._shell._unquote(module_args[key])
298
-
299
290
  result = self._shared_loader_obj.module_loader.find_plugin_with_context(module_name, mod_type, collection_list=self._task.collections)
300
291
 
301
292
  if not result.resolved:
@@ -680,8 +671,18 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
680
671
  become_user,
681
672
  setfacl_mode)
682
673
 
683
- if res['rc'] == 0:
684
- return remote_paths
674
+ match res.get('rc'):
675
+ case 0:
676
+ return remote_paths
677
+ case 2:
678
+ # invalid syntax (for example, missing user, missing colon)
679
+ self._display.debug(f"setfacl command failed with an invalid syntax. Trying chmod instead. Err: {res!r}")
680
+ case 127:
681
+ # setfacl binary does not exists or we don't have permission to use it.
682
+ self._display.debug(f"setfacl binary does not exist or does not have permission to use it. Trying chmod instead. Err: {res!r}")
683
+ case _:
684
+ # generic debug message
685
+ self._display.debug(f'Failed to set facl {setfacl_mode}, got:{res!r}')
685
686
 
686
687
  # Step 3b: Set execute if we need to. We do this before anything else
687
688
  # because some of the methods below might work but not let us set
@@ -874,7 +875,7 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
874
875
  # happens sometimes when it is a dir and not on bsd
875
876
  if 'checksum' not in mystat['stat']:
876
877
  mystat['stat']['checksum'] = ''
877
- elif not isinstance(mystat['stat']['checksum'], string_types):
878
+ elif not isinstance(mystat['stat']['checksum'], str):
878
879
  raise AnsibleError("Invalid checksum returned by stat: expected a string type but got %s" % type(mystat['stat']['checksum']))
879
880
 
880
881
  return mystat['stat']
@@ -1084,7 +1085,7 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
1084
1085
  # the remote system, which can be read and parsed by the module
1085
1086
  args_data = ""
1086
1087
  for k, v in module_args.items():
1087
- args_data += '%s=%s ' % (k, shlex.quote(text_type(v)))
1088
+ args_data += '%s=%s ' % (k, shlex.quote(str(v)))
1088
1089
  self._transfer_data(args_file_path, args_data)
1089
1090
  elif module_style in ('non_native_want_json', 'binary'):
1090
1091
  profile_encoder = get_module_encoder(module_bits.serialization_profile, Direction.CONTROLLER_TO_MODULE)
@@ -1169,7 +1170,7 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
1169
1170
  self._cleanup_remote_tmp = False
1170
1171
 
1171
1172
  # NOTE: dnf returns results .. but that made it 'compatible' with squashing, so we allow mappings, for now
1172
- if 'results' in data and (not isinstance(data['results'], Sequence) or isinstance(data['results'], string_types)):
1173
+ if 'results' in data and (not isinstance(data['results'], Sequence) or isinstance(data['results'], str)):
1173
1174
  data['ansible_module_results'] = data['results']
1174
1175
  del data['results']
1175
1176
  display.warning("Found internal 'results' key in module return, renamed to 'ansible_module_results'.")
@@ -1322,16 +1323,16 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
1322
1323
 
1323
1324
  # stdout and stderr may be either a file-like or a bytes object.
1324
1325
  # Convert either one to a text type
1325
- if isinstance(stdout, binary_type):
1326
+ if isinstance(stdout, bytes):
1326
1327
  out = to_text(stdout, errors=encoding_errors)
1327
- elif not isinstance(stdout, text_type):
1328
+ elif not isinstance(stdout, str):
1328
1329
  out = to_text(b''.join(stdout.readlines()), errors=encoding_errors)
1329
1330
  else:
1330
1331
  out = stdout
1331
1332
 
1332
- if isinstance(stderr, binary_type):
1333
+ if isinstance(stderr, bytes):
1333
1334
  err = to_text(stderr, errors=encoding_errors)
1334
- elif not isinstance(stderr, text_type):
1335
+ elif not isinstance(stderr, str):
1335
1336
  err = to_text(b''.join(stderr.readlines()), errors=encoding_errors)
1336
1337
  else:
1337
1338
  err = stderr
@@ -21,7 +21,6 @@ from __future__ import annotations
21
21
  from collections.abc import Mapping
22
22
 
23
23
  from ansible.errors import AnsibleActionFail
24
- from ansible.module_utils.six import string_types
25
24
  from ansible.plugins.action import ActionBase
26
25
  from ansible.parsing.utils.addresses import parse_address
27
26
  from ansible.utils.display import Display
@@ -74,7 +73,7 @@ class ActionModule(ActionBase):
74
73
  if groups:
75
74
  if isinstance(groups, list):
76
75
  group_list = groups
77
- elif isinstance(groups, string_types):
76
+ elif isinstance(groups, str):
78
77
  group_list = groups.split(",")
79
78
  else:
80
79
  raise AnsibleActionFail("Groups must be specified as a list.", obj=groups)
@@ -20,7 +20,6 @@ import os
20
20
  import base64
21
21
  from ansible.errors import AnsibleConnectionFailure, AnsibleError, AnsibleActionFail, AnsibleActionSkip
22
22
  from ansible.module_utils.common.text.converters import to_bytes, to_text
23
- from ansible.module_utils.six import string_types
24
23
  from ansible.module_utils.parsing.convert_bool import boolean
25
24
  from ansible.plugins.action import ActionBase
26
25
  from ansible.utils.display import Display
@@ -52,10 +51,10 @@ class ActionModule(ActionBase):
52
51
 
53
52
  msg = ''
54
53
  # FIXME: validate source and dest are strings; use basic.py and module specs
55
- if not isinstance(source, string_types):
54
+ if not isinstance(source, str):
56
55
  msg = "Invalid type supplied for source option, it must be a string"
57
56
 
58
- if not isinstance(dest, string_types):
57
+ if not isinstance(dest, str):
59
58
  msg = "Invalid type supplied for dest option, it must be a string"
60
59
 
61
60
  if source is None or dest is None:
@@ -131,7 +130,6 @@ class ActionModule(ActionBase):
131
130
 
132
131
  # calculate the destination name
133
132
  if os.path.sep not in self._connection._shell.join_path('a', ''):
134
- source = self._connection._shell._unquote(source)
135
133
  source_local = source.replace('\\', '/')
136
134
  else:
137
135
  source_local = source
@@ -194,7 +192,7 @@ class ActionModule(ActionBase):
194
192
  msg="checksum mismatch", file=source, dest=dest, remote_md5sum=None,
195
193
  checksum=new_checksum, remote_checksum=remote_checksum))
196
194
  else:
197
- result.update({'changed': True, 'md5sum': new_md5, 'dest': dest,
195
+ result.update({'changed': True, 'md5sum': new_md5, 'file': source, 'dest': dest,
198
196
  'remote_md5sum': None, 'checksum': new_checksum,
199
197
  'remote_checksum': remote_checksum})
200
198
  else:
@@ -17,7 +17,6 @@
17
17
  from __future__ import annotations
18
18
 
19
19
  from ansible.plugins.action import ActionBase
20
- from ansible.module_utils.six import string_types
21
20
 
22
21
 
23
22
  class ActionModule(ActionBase):
@@ -42,7 +41,7 @@ class ActionModule(ActionBase):
42
41
 
43
42
  group_name = self._task.args.get('key')
44
43
  parent_groups = self._task.args.get('parents', ['all'])
45
- if isinstance(parent_groups, string_types):
44
+ if isinstance(parent_groups, str):
46
45
  parent_groups = [parent_groups]
47
46
 
48
47
  result['changed'] = False
@@ -10,7 +10,6 @@ import pathlib
10
10
  import ansible.constants as C
11
11
  from ansible.errors import AnsibleError
12
12
  from ansible._internal._datatag._tags import SourceWasEncrypted
13
- from ansible.module_utils.six import string_types
14
13
  from ansible.module_utils.common.text.converters import to_native
15
14
  from ansible.plugins.action import ActionBase
16
15
  from ansible.utils.vars import combine_vars
@@ -38,14 +37,17 @@ class ActionModule(ActionBase):
38
37
  if not self.ignore_files:
39
38
  self.ignore_files = list()
40
39
 
41
- if isinstance(self.ignore_files, string_types):
40
+ if isinstance(self.ignore_files, str):
41
+ self._display.deprecated(
42
+ msg="Specifying 'ignore_files' as a string is deprecated.",
43
+ version="2.24",
44
+ help_text="Use a list of strings instead.",
45
+ obj=self.ignore_files,
46
+ )
42
47
  self.ignore_files = self.ignore_files.split()
43
48
 
44
- elif isinstance(self.ignore_files, dict):
45
- return {
46
- 'failed': True,
47
- 'message': '{0} must be a list'.format(self.ignore_files)
48
- }
49
+ if not isinstance(self.ignore_files, list):
50
+ raise AnsibleError("The 'ignore_files' option must be a list.", obj=self.ignore_files)
49
51
 
50
52
  def _set_args(self):
51
53
  """ Set instance variables based on the arguments that were passed """
@@ -65,11 +67,8 @@ class ActionModule(ActionBase):
65
67
  self.ignore_files = self._task.args.get('ignore_files', None)
66
68
  self.valid_extensions = self._task.args.get('extensions', self.VALID_FILE_EXTENSIONS)
67
69
 
68
- # convert/validate extensions list
69
- if isinstance(self.valid_extensions, string_types):
70
- self.valid_extensions = list(self.valid_extensions)
71
70
  if not isinstance(self.valid_extensions, list):
72
- raise AnsibleError('Invalid type for "extensions" option, it must be a list')
71
+ raise AnsibleError("The 'extensions' option must be a list.", obj=self.valid_extensions)
73
72
 
74
73
  def run(self, tmp=None, task_vars=None):
75
74
  """ Load yml files recursively from a directory.
@@ -93,10 +92,10 @@ class ActionModule(ActionBase):
93
92
  elif arg in self.VALID_ALL:
94
93
  pass
95
94
  else:
96
- raise AnsibleError('{0} is not a valid option in include_vars'.format(to_native(arg)))
95
+ raise AnsibleError(f'{arg} is not a valid option in include_vars', obj=arg)
97
96
 
98
97
  if dirs and files:
99
- raise AnsibleError("You are mixing file only and dir only arguments, these are incompatible")
98
+ raise AnsibleError("You are mixing file only and dir only arguments, these are incompatible", obj=self._task.args)
100
99
 
101
100
  # set internal vars from args
102
101
  self._set_args()
@@ -108,13 +107,13 @@ class ActionModule(ActionBase):
108
107
  self._set_root_dir()
109
108
  if not path.exists(self.source_dir):
110
109
  failed = True
111
- err_msg = ('{0} directory does not exist'.format(to_native(self.source_dir)))
110
+ err_msg = f"{self.source_dir} directory does not exist"
112
111
  elif not path.isdir(self.source_dir):
113
112
  failed = True
114
- err_msg = ('{0} is not a directory'.format(to_native(self.source_dir)))
113
+ err_msg = f"{self.source_dir} is not a directory"
115
114
  else:
116
115
  for root_dir, filenames in self._traverse_dir_depth():
117
- failed, err_msg, updated_results = (self._load_files_in_dir(root_dir, filenames))
116
+ failed, err_msg, updated_results = self._load_files_in_dir(root_dir, filenames)
118
117
  if failed:
119
118
  break
120
119
  results.update(updated_results)
@@ -175,7 +174,7 @@ class ActionModule(ActionBase):
175
174
  self.source_dir = path.join(current_dir, self.source_dir)
176
175
 
177
176
  def _log_walk(self, error):
178
- self._display.vvv('Issue with walking through "%s": %s' % (to_native(error.filename), to_native(error)))
177
+ self._display.vvv(f"Issue with walking through {error.filename}: {error}")
179
178
 
180
179
  def _traverse_dir_depth(self):
181
180
  """ Recursively iterate over a directory and sort the files in
@@ -204,9 +203,8 @@ class ActionModule(ActionBase):
204
203
  try:
205
204
  if re.search(r'{0}$'.format(file_type), filename):
206
205
  return True
207
- except Exception:
208
- err_msg = 'Invalid regular expression: {0}'.format(file_type)
209
- raise AnsibleError(err_msg)
206
+ except Exception as ex:
207
+ raise AnsibleError(f'Invalid regular expression: {file_type!r}', obj=file_type) from ex
210
208
  return False
211
209
 
212
210
  def _is_valid_file_ext(self, source_file):
@@ -232,7 +230,7 @@ class ActionModule(ActionBase):
232
230
  err_msg = ''
233
231
  if validate_extensions and not self._is_valid_file_ext(filename):
234
232
  failed = True
235
- err_msg = ('{0} does not have a valid extension: {1}'.format(to_native(filename), ', '.join(self.valid_extensions)))
233
+ err_msg = f"{filename!r} does not have a valid extension: {', '.join(self.valid_extensions)}"
236
234
  else:
237
235
  data = self._loader.load_from_file(filename, cache='none', trusted_as_template=True)
238
236
 
@@ -243,7 +241,7 @@ class ActionModule(ActionBase):
243
241
 
244
242
  if not isinstance(data, dict):
245
243
  failed = True
246
- err_msg = ('{0} must be stored as a dictionary/hash'.format(to_native(filename)))
244
+ err_msg = f"{filename!r} must be stored as a dictionary/hash"
247
245
  else:
248
246
  self.included_files.append(filename)
249
247
  results.update(data)