ansible-core 2.19.2__py3-none-any.whl → 2.20.0b1__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 (202) 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/_display_utils.py +145 -0
  5. ansible/_internal/_json/__init__.py +3 -4
  6. ansible/_internal/_templating/_engine.py +1 -1
  7. ansible/_internal/_templating/_jinja_plugins.py +1 -2
  8. ansible/_internal/_wrapt.py +105 -301
  9. ansible/cli/__init__.py +11 -10
  10. ansible/cli/adhoc.py +1 -2
  11. ansible/cli/arguments/option_helpers.py +1 -1
  12. ansible/cli/config.py +5 -6
  13. ansible/cli/doc.py +67 -67
  14. ansible/cli/galaxy.py +15 -24
  15. ansible/cli/inventory.py +0 -1
  16. ansible/cli/playbook.py +0 -1
  17. ansible/cli/pull.py +0 -1
  18. ansible/cli/scripts/ansible_connection_cli_stub.py +1 -1
  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/powershell/async_watchdog.ps1 +24 -4
  24. ansible/executor/task_executor.py +32 -22
  25. ansible/executor/task_queue_manager.py +1 -3
  26. ansible/galaxy/api.py +33 -80
  27. ansible/galaxy/collection/__init__.py +4 -17
  28. ansible/galaxy/dependency_resolution/dataclasses.py +0 -10
  29. ansible/galaxy/dependency_resolution/providers.py +1 -2
  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 +27 -24
  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/urls.py +6 -2
  58. ansible/module_utils/yumdnf.py +0 -5
  59. ansible/modules/apt.py +18 -13
  60. ansible/modules/apt_repository.py +1 -1
  61. ansible/modules/assemble.py +5 -9
  62. ansible/modules/blockinfile.py +39 -23
  63. ansible/modules/cron.py +26 -35
  64. ansible/modules/deb822_repository.py +83 -12
  65. ansible/modules/dnf.py +3 -7
  66. ansible/modules/dnf5.py +4 -6
  67. ansible/modules/expect.py +0 -3
  68. ansible/modules/find.py +1 -2
  69. ansible/modules/get_url.py +1 -1
  70. ansible/modules/git.py +4 -5
  71. ansible/modules/include_vars.py +1 -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/mod_args.py +3 -0
  85. ansible/parsing/vault/__init__.py +1 -2
  86. ansible/playbook/base.py +8 -56
  87. ansible/playbook/block.py +0 -60
  88. ansible/playbook/collectionsearch.py +1 -2
  89. ansible/playbook/handler.py +1 -7
  90. ansible/playbook/helpers.py +0 -7
  91. ansible/playbook/included_file.py +1 -1
  92. ansible/playbook/play.py +103 -37
  93. ansible/playbook/play_context.py +4 -0
  94. ansible/playbook/role/__init__.py +10 -65
  95. ansible/playbook/role/definition.py +3 -4
  96. ansible/playbook/role/include.py +2 -3
  97. ansible/playbook/role/metadata.py +1 -12
  98. ansible/playbook/role/requirement.py +1 -2
  99. ansible/playbook/role_include.py +1 -2
  100. ansible/playbook/taggable.py +16 -5
  101. ansible/playbook/task.py +51 -55
  102. ansible/plugins/action/__init__.py +20 -19
  103. ansible/plugins/action/add_host.py +1 -2
  104. ansible/plugins/action/fetch.py +2 -4
  105. ansible/plugins/action/group_by.py +1 -2
  106. ansible/plugins/action/include_vars.py +20 -22
  107. ansible/plugins/action/script.py +1 -3
  108. ansible/plugins/action/template.py +1 -2
  109. ansible/plugins/action/uri.py +4 -2
  110. ansible/plugins/cache/__init__.py +1 -0
  111. ansible/plugins/callback/__init__.py +13 -6
  112. ansible/plugins/connection/__init__.py +3 -7
  113. ansible/plugins/connection/local.py +2 -3
  114. ansible/plugins/connection/psrp.py +0 -2
  115. ansible/plugins/connection/ssh.py +2 -7
  116. ansible/plugins/connection/winrm.py +0 -2
  117. ansible/plugins/doc_fragments/result_format_callback.py +15 -0
  118. ansible/plugins/filter/core.py +4 -5
  119. ansible/plugins/filter/encryption.py +3 -27
  120. ansible/plugins/filter/mathstuff.py +1 -2
  121. ansible/plugins/filter/to_nice_yaml.yml +31 -3
  122. ansible/plugins/filter/to_yaml.yml +29 -12
  123. ansible/plugins/inventory/__init__.py +1 -2
  124. ansible/plugins/inventory/script.py +2 -1
  125. ansible/plugins/inventory/toml.py +3 -6
  126. ansible/plugins/inventory/yaml.py +1 -2
  127. ansible/plugins/list.py +10 -3
  128. ansible/plugins/loader.py +6 -6
  129. ansible/plugins/lookup/password.py +1 -2
  130. ansible/plugins/lookup/subelements.py +2 -3
  131. ansible/plugins/lookup/url.py +1 -1
  132. ansible/plugins/lookup/varnames.py +1 -2
  133. ansible/plugins/shell/__init__.py +9 -4
  134. ansible/plugins/shell/powershell.py +8 -24
  135. ansible/plugins/strategy/__init__.py +6 -3
  136. ansible/plugins/test/core.py +4 -1
  137. ansible/plugins/test/regex.yml +18 -6
  138. ansible/release.py +2 -2
  139. ansible/template/__init__.py +3 -7
  140. ansible/utils/collection_loader/_collection_config.py +5 -0
  141. ansible/utils/collection_loader/_collection_finder.py +11 -14
  142. ansible/utils/context_objects.py +7 -4
  143. ansible/utils/display.py +28 -167
  144. ansible/utils/encrypt.py +0 -5
  145. ansible/utils/helpers.py +6 -2
  146. ansible/utils/jsonrpc.py +7 -3
  147. ansible/utils/plugin_docs.py +49 -38
  148. ansible/utils/ssh_functions.py +0 -19
  149. ansible/utils/unsafe_proxy.py +7 -7
  150. ansible/vars/clean.py +2 -3
  151. ansible/vars/manager.py +27 -20
  152. ansible/vars/plugins.py +1 -31
  153. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/METADATA +3 -3
  154. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/RECORD +200 -200
  155. ansible_test/_data/completion/docker.txt +7 -7
  156. ansible_test/_data/completion/network.txt +0 -1
  157. ansible_test/_data/completion/remote.txt +4 -4
  158. ansible_test/_data/requirements/ansible-test.txt +1 -1
  159. ansible_test/_data/requirements/sanity.changelog.txt +1 -1
  160. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  161. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  162. ansible_test/_internal/cache.py +2 -5
  163. ansible_test/_internal/cli/compat.py +1 -1
  164. ansible_test/_internal/commands/coverage/combine.py +1 -3
  165. ansible_test/_internal/commands/integration/__init__.py +3 -7
  166. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  167. ansible_test/_internal/commands/integration/coverage.py +1 -3
  168. ansible_test/_internal/commands/integration/filters.py +5 -10
  169. ansible_test/_internal/commands/sanity/validate_modules.py +1 -5
  170. ansible_test/_internal/commands/units/__init__.py +1 -13
  171. ansible_test/_internal/completion.py +2 -5
  172. ansible_test/_internal/config.py +2 -7
  173. ansible_test/_internal/coverage_util.py +1 -1
  174. ansible_test/_internal/delegation.py +2 -0
  175. ansible_test/_internal/docker_util.py +1 -1
  176. ansible_test/_internal/host_profiles.py +6 -11
  177. ansible_test/_internal/provider/__init__.py +2 -5
  178. ansible_test/_internal/provisioning.py +2 -5
  179. ansible_test/_internal/pypi_proxy.py +1 -1
  180. ansible_test/_internal/target.py +2 -6
  181. ansible_test/_internal/thread.py +1 -4
  182. ansible_test/_internal/util.py +9 -14
  183. ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +14 -19
  184. ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +30 -27
  185. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -18
  186. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -2
  187. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +59 -71
  188. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -2
  189. ansible_test/_util/target/cli/ansible_test_cli_stub.py +4 -2
  190. ansible_test/_util/target/common/constants.py +2 -2
  191. ansible_test/_util/target/setup/bootstrap.sh +0 -6
  192. ansible/utils/py3compat.py +0 -27
  193. ansible_test/_data/pytest/config/legacy.ini +0 -4
  194. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/WHEEL +0 -0
  195. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/entry_points.txt +0 -0
  196. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/COPYING +0 -0
  197. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  198. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  199. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  200. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  201. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  202. {ansible_core-2.19.2.dist-info → ansible_core-2.20.0b1.dist-info}/top_level.txt +0 -0
@@ -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
@@ -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)
@@ -139,8 +139,6 @@ class ActionModule(ActionBase):
139
139
  else:
140
140
  script_cmd = ' '.join([env_string, target_command])
141
141
 
142
- script_cmd = self._connection._shell.wrap_for_exec(script_cmd)
143
-
144
142
  exec_data = None
145
143
  # PowerShell runs the script in a special wrapper to enable things
146
144
  # like become and environment args
@@ -149,7 +147,7 @@ class ActionModule(ActionBase):
149
147
  pc = self._task
150
148
  exec_data = ps_manifest._create_powershell_wrapper(
151
149
  name=f"ansible.builtin.script.{pathlib.Path(source).stem}",
152
- module_data=to_bytes(script_cmd),
150
+ module_data=to_bytes(f"& {script_cmd}; exit $LASTEXITCODE"),
153
151
  module_path=source,
154
152
  module_args={},
155
153
  environment=env_dict,
@@ -23,7 +23,6 @@ from ansible.config.manager import ensure_type
23
23
  from ansible.errors import AnsibleError, AnsibleActionFail
24
24
  from ansible.module_utils.common.text.converters import to_bytes, to_text, to_native
25
25
  from ansible.module_utils.parsing.convert_bool import boolean
26
- from ansible.module_utils.six import string_types
27
26
  from ansible.plugins.action import ActionBase
28
27
  from ansible.template import trust_as_template
29
28
  from ansible._internal._templating import _template_vars
@@ -49,7 +48,7 @@ class ActionModule(ActionBase):
49
48
  'block_end_string', 'comment_start_string', 'comment_end_string'):
50
49
  if s_type in self._task.args:
51
50
  value = ensure_type(self._task.args[s_type], 'string')
52
- if value is not None and not isinstance(value, string_types):
51
+ if value is not None and not isinstance(value, str):
53
52
  raise AnsibleActionFail("%s is expected to be a string, but got %s instead" % (s_type, type(value)))
54
53
  self._task.args[s_type] = value
55
54
 
@@ -7,6 +7,7 @@ from __future__ import annotations
7
7
 
8
8
  import collections.abc as _c
9
9
  import os
10
+ from copy import deepcopy
10
11
 
11
12
  from ansible.errors import AnsibleActionFail
12
13
  from ansible.module_utils.parsing.convert_bool import boolean
@@ -53,7 +54,8 @@ class ActionModule(ActionBase):
53
54
  raise AnsibleActionFail(
54
55
  'body must be mapping, cannot be type %s' % body.__class__.__name__
55
56
  )
56
- for field, value in body.items():
57
+ new_body = deepcopy(body)
58
+ for field, value in new_body.items():
57
59
  if not isinstance(value, _c.MutableMapping):
58
60
  continue
59
61
  content = value.get('content')
@@ -70,7 +72,7 @@ class ActionModule(ActionBase):
70
72
  value['filename'] = tmp_src
71
73
  self._transfer_file(filename, tmp_src)
72
74
  self._fixup_perms2((self._connection._shell.tmpdir, tmp_src))
73
- kwargs['body'] = body
75
+ kwargs['body'] = new_body
74
76
 
75
77
  new_module_args = self._task.args | kwargs
76
78
 
@@ -162,6 +162,7 @@ class BaseFileCacheModule(BaseCacheModule):
162
162
  except OSError as ex:
163
163
  display.error_as_warning(f"Error in {self.plugin_name!r} cache plugin while trying to write to {tmpfile_path!r}.", exception=ex)
164
164
  try:
165
+ os.close(tmpfile_handle) # os.rename fails if handle is still open in WSL
165
166
  os.rename(tmpfile_path, cachefile)
166
167
  os.chmod(cachefile, mode=S_IRWU_RG_RO)
167
168
  except OSError as ex:
@@ -60,9 +60,6 @@ _YAML_BREAK_CHARS = '\n\x85\u2028\u2029' # NL, NEL, LS, PS
60
60
  _SPACE_BREAK_RE = re.compile(fr' +([{_YAML_BREAK_CHARS}])')
61
61
 
62
62
 
63
- _T_callable = t.TypeVar("_T_callable", bound=t.Callable)
64
-
65
-
66
63
  class _AnsibleCallbackDumper(_dumper.AnsibleDumper):
67
64
  def __init__(self, *args, lossy: bool = False, **kwargs):
68
65
  super().__init__(*args, **kwargs)
@@ -293,7 +290,11 @@ class CallbackBase(AnsiblePlugin):
293
290
  )
294
291
 
295
292
  if not indent and any(indent_conditions):
296
- indent = 4
293
+ try:
294
+ indent = self.get_option('result_indentation')
295
+ except KeyError:
296
+ # Callback does not declare result_indentation nor extend result_format_callback
297
+ indent = 4
297
298
  if pretty_results is False:
298
299
  # pretty_results=False overrides any specified indentation
299
300
  indent = None
@@ -394,8 +395,14 @@ class CallbackBase(AnsiblePlugin):
394
395
  # Callback does not declare pretty_results nor extend result_format_callback
395
396
  pretty_results = None
396
397
 
398
+ try:
399
+ indent = self.get_option('result_indentation')
400
+ except KeyError:
401
+ # Callback does not declare result_indentation nor extend result_format_callback
402
+ indent = 4
403
+
397
404
  if result_format == 'json':
398
- return json.dumps(diff, sort_keys=True, indent=4, separators=(u',', u': ')) + u'\n'
405
+ return json.dumps(diff, sort_keys=True, indent=indent, separators=(u',', u': ')) + u'\n'
399
406
 
400
407
  if result_format == 'yaml':
401
408
  # None is a sentinel in this case that indicates default behavior
@@ -407,7 +414,7 @@ class CallbackBase(AnsiblePlugin):
407
414
  allow_unicode=True,
408
415
  Dumper=functools.partial(_AnsibleCallbackDumper, lossy=lossy),
409
416
  default_flow_style=False,
410
- indent=4,
417
+ indent=indent,
411
418
  # sort_keys=sort_keys # This requires PyYAML>=5.1
412
419
  ),
413
420
  ' '
@@ -6,7 +6,6 @@ from __future__ import annotations
6
6
 
7
7
  import collections.abc as c
8
8
  import fcntl
9
- import io
10
9
  import os
11
10
  import shlex
12
11
  import typing as t
@@ -16,7 +15,7 @@ from functools import wraps
16
15
 
17
16
  from ansible import constants as C
18
17
  from ansible.errors import AnsibleValueOmittedError
19
- from ansible.module_utils.common.text.converters import to_bytes, to_text
18
+ from ansible.module_utils.common.text.converters import to_text
20
19
  from ansible.playbook.play_context import PlayContext
21
20
  from ansible.plugins import AnsiblePlugin
22
21
  from ansible.plugins.become import BecomeBase
@@ -32,9 +31,6 @@ __all__ = ['ConnectionBase', 'ensure_connect']
32
31
 
33
32
  BUFSIZE = 65536
34
33
 
35
- P = t.ParamSpec('P')
36
- T = t.TypeVar('T')
37
-
38
34
 
39
35
  class ConnectionKwargs(t.TypedDict):
40
36
  task_uuid: str
@@ -42,7 +38,7 @@ class ConnectionKwargs(t.TypedDict):
42
38
  shell: t.NotRequired[ShellBase]
43
39
 
44
40
 
45
- def ensure_connect(
41
+ def ensure_connect[T, **P](
46
42
  func: c.Callable[t.Concatenate[ConnectionBase, P], T],
47
43
  ) -> c.Callable[t.Concatenate[ConnectionBase, P], T]:
48
44
  @wraps(func)
@@ -135,7 +131,7 @@ class ConnectionBase(AnsiblePlugin):
135
131
  pass
136
132
 
137
133
  @abstractmethod
138
- def _connect(self: T) -> T:
134
+ def _connect[T](self: T) -> T:
139
135
  """Connect to the host we've been initialized with"""
140
136
 
141
137
  @ensure_connect
@@ -47,7 +47,6 @@ import typing as t
47
47
 
48
48
  import ansible.constants as C
49
49
  from ansible.errors import AnsibleError, AnsibleFileNotFound, AnsibleConnectionFailure
50
- from ansible.module_utils.six import text_type, binary_type
51
50
  from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
52
51
  from ansible.plugins.connection import ConnectionBase
53
52
  from ansible.utils.display import Display
@@ -100,7 +99,7 @@ class Connection(ConnectionBase):
100
99
  display.vvv(u"EXEC {0}".format(to_text(cmd)), host=self._play_context.remote_addr)
101
100
  display.debug("opening command with Popen()")
102
101
 
103
- if isinstance(cmd, (text_type, binary_type)):
102
+ if isinstance(cmd, (str, bytes)):
104
103
  cmd = to_text(cmd)
105
104
  else:
106
105
  cmd = map(to_text, cmd)
@@ -119,7 +118,7 @@ class Connection(ConnectionBase):
119
118
 
120
119
  p = subprocess.Popen(
121
120
  cmd,
122
- shell=isinstance(cmd, (text_type, binary_type)),
121
+ shell=isinstance(cmd, (str, bytes)),
123
122
  executable=executable,
124
123
  cwd=self.cwd,
125
124
  stdin=stdin,
@@ -489,7 +489,6 @@ class Connection(ConnectionBase):
489
489
  def put_file(self, in_path: str, out_path: str) -> None:
490
490
  super(Connection, self).put_file(in_path, out_path)
491
491
 
492
- out_path = self._shell._unquote(out_path)
493
492
  display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._psrp_host)
494
493
 
495
494
  script, in_data = _bootstrap_powershell_script('psrp_put_file.ps1', {
@@ -549,7 +548,6 @@ class Connection(ConnectionBase):
549
548
  display.vvv("FETCH %s TO %s" % (in_path, out_path),
550
549
  host=self._psrp_host)
551
550
 
552
- in_path = self._shell._unquote(in_path)
553
551
  out_path = out_path.replace('\\', '/')
554
552
  b_out_path = to_bytes(out_path, errors='surrogate_or_strict')
555
553
 
@@ -34,8 +34,6 @@ DOCUMENTATION = """
34
34
  - name: inventory_hostname
35
35
  - name: ansible_host
36
36
  - name: ansible_ssh_host
37
- - name: delegated_vars['ansible_host']
38
- - name: delegated_vars['ansible_ssh_host']
39
37
  host_key_checking:
40
38
  description: Determines if SSH should reject or not a connection after checking host keys.
41
39
  default: True
@@ -443,7 +441,6 @@ from ansible.errors import (
443
441
  AnsibleError,
444
442
  AnsibleFileNotFound,
445
443
  )
446
- from ansible.module_utils.six import text_type, binary_type
447
444
  from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
448
445
  from ansible.plugins.connection import ConnectionBase, BUFSIZE
449
446
  from ansible.plugins.shell.powershell import _replace_stderr_clixml
@@ -461,8 +458,6 @@ else:
461
458
 
462
459
  display = Display()
463
460
 
464
- P = t.ParamSpec('P')
465
-
466
461
  # error messages that indicate 255 return code is not from ssh itself.
467
462
  b_NOT_SSH_ERRORS = (b'Traceback (most recent call last):', # Python-2.6 when there's an exception
468
463
  # while invoking a script via -m
@@ -549,7 +544,7 @@ def _handle_error(
549
544
  display.vvv(msg, host=host)
550
545
 
551
546
 
552
- def _ssh_retry(
547
+ def _ssh_retry[**P](
553
548
  func: c.Callable[t.Concatenate[Connection, P], tuple[int, bytes, bytes]],
554
549
  ) -> c.Callable[t.Concatenate[Connection, P], tuple[int, bytes, bytes]]:
555
550
  """
@@ -1126,7 +1121,7 @@ class Connection(ConnectionBase):
1126
1121
 
1127
1122
  p = None
1128
1123
 
1129
- if isinstance(cmd, (text_type, binary_type)):
1124
+ if isinstance(cmd, (str, bytes)):
1130
1125
  cmd = to_bytes(cmd)
1131
1126
  else:
1132
1127
  cmd = list(map(to_bytes, cmd))
@@ -768,7 +768,6 @@ class Connection(ConnectionBase):
768
768
 
769
769
  def put_file(self, in_path: str, out_path: str) -> None:
770
770
  super(Connection, self).put_file(in_path, out_path)
771
- out_path = self._shell._unquote(out_path)
772
771
  display.vvv('PUT "%s" TO "%s"' % (in_path, out_path), host=self._winrm_host)
773
772
  if not os.path.exists(to_bytes(in_path, errors='surrogate_or_strict')):
774
773
  raise AnsibleFileNotFound('file or module does not exist: "%s"' % to_native(in_path))
@@ -806,7 +805,6 @@ class Connection(ConnectionBase):
806
805
 
807
806
  def fetch_file(self, in_path: str, out_path: str) -> None:
808
807
  super(Connection, self).fetch_file(in_path, out_path)
809
- in_path = self._shell._unquote(in_path)
810
808
  out_path = out_path.replace('\\', '/')
811
809
  # consistent with other connection plugins, we assume the caller has created the target dir
812
810
  display.vvv('FETCH "%s" TO "%s"' % (in_path, out_path), host=self._winrm_host)
@@ -26,6 +26,21 @@ class ModuleDocFragment(object):
26
26
  - json
27
27
  - yaml
28
28
  version_added: '2.13'
29
+ result_indentation:
30
+ name: Indentation of the result
31
+ description:
32
+ - Allows to configure indentation for YAML and verbose/pretty JSON.
33
+ - Please note that for O(result_format=yaml), only values between 2 and 9 will be handled as expected by PyYAML.
34
+ If indentation is set to 1, or to 10 or larger, the first level of indentation will be used,
35
+ but all further indentations will be by 2 spaces.
36
+ type: int
37
+ default: 4
38
+ env:
39
+ - name: ANSIBLE_CALLBACK_RESULT_INDENTATION
40
+ ini:
41
+ - key: callback_result_indentation
42
+ section: defaults
43
+ version_added: '2.20'
29
44
  pretty_results:
30
45
  name: Configure output for readability
31
46
  description:
@@ -29,7 +29,6 @@ from ansible._internal._templating import _lazy_containers
29
29
  from ansible.errors import AnsibleFilterError, AnsibleTypeError, AnsibleTemplatePluginError
30
30
  from ansible.module_utils.datatag import native_type_name
31
31
  from ansible.module_utils.common.json import get_encoder, get_decoder
32
- from ansible.module_utils.six import string_types, integer_types, text_type
33
32
  from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
34
33
  from ansible.module_utils.common.collections import is_sequence
35
34
  from ansible.parsing.yaml.dumper import AnsibleDumper
@@ -279,7 +278,7 @@ def rand(environment, end, start=None, step=None, seed=None):
279
278
  r = SystemRandom()
280
279
  else:
281
280
  r = Random(seed)
282
- if isinstance(end, integer_types):
281
+ if isinstance(end, int):
283
282
  if not start:
284
283
  start = 0
285
284
  if not step:
@@ -556,7 +555,7 @@ def subelements(obj, subelements, skip_missing=False):
556
555
 
557
556
  if isinstance(subelements, list):
558
557
  subelement_list = subelements[:]
559
- elif isinstance(subelements, string_types):
558
+ elif isinstance(subelements, str):
560
559
  subelement_list = subelements.split('.')
561
560
  else:
562
561
  raise AnsibleTypeError('subelements must be a list or a string')
@@ -618,7 +617,7 @@ def list_of_dict_key_value_elements_to_dict(mylist, key_name='key', value_name='
618
617
  def path_join(paths):
619
618
  """ takes a sequence or a string, and return a concatenation
620
619
  of the different members """
621
- if isinstance(paths, string_types):
620
+ if isinstance(paths, str):
622
621
  return os.path.join(paths)
623
622
  if is_sequence(paths):
624
623
  return os.path.join(*paths)
@@ -810,7 +809,7 @@ class FilterModule(object):
810
809
  'dict2items': dict_to_list_of_dict_key_value_elements,
811
810
  'items2dict': list_of_dict_key_value_elements_to_dict,
812
811
  'subelements': subelements,
813
- 'split': partial(unicode_wrap, text_type.split),
812
+ 'split': partial(unicode_wrap, str.split),
814
813
  # FDI038 - replace this with a standard type compat shim
815
814
  'groupby': _cleansed_groupby,
816
815