ansible-core 2.19.0b5__py3-none-any.whl → 2.19.0b7__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.
Files changed (184) hide show
  1. ansible/_internal/_ansiballz/__init__.py +0 -0
  2. ansible/_internal/_ansiballz/_builder.py +101 -0
  3. ansible/_internal/{_ansiballz.py → _ansiballz/_wrapper.py} +11 -11
  4. ansible/_internal/_templating/_jinja_bits.py +22 -4
  5. ansible/_internal/_templating/_jinja_common.py +1 -1
  6. ansible/_internal/_templating/_jinja_plugins.py +5 -2
  7. ansible/_internal/_templating/_template_vars.py +72 -0
  8. ansible/_internal/_templating/_transform.py +6 -0
  9. ansible/_internal/_yaml/_constructor.py +4 -4
  10. ansible/_internal/_yaml/_dumper.py +26 -18
  11. ansible/cli/__init__.py +9 -14
  12. ansible/cli/adhoc.py +6 -3
  13. ansible/cli/arguments/option_helpers.py +1 -1
  14. ansible/cli/console.py +2 -2
  15. ansible/cli/doc.py +4 -4
  16. ansible/cli/inventory.py +5 -7
  17. ansible/config/base.yml +33 -6
  18. ansible/errors/__init__.py +2 -1
  19. ansible/executor/module_common.py +75 -44
  20. ansible/executor/powershell/psrp_put_file.ps1 +1 -1
  21. ansible/executor/process/worker.py +2 -2
  22. ansible/executor/task_executor.py +2 -2
  23. ansible/executor/task_queue_manager.py +34 -70
  24. ansible/executor/task_result.py +1 -1
  25. ansible/galaxy/api.py +3 -6
  26. ansible/galaxy/collection/__init__.py +1 -6
  27. ansible/galaxy/collection/concrete_artifact_manager.py +4 -10
  28. ansible/galaxy/dependency_resolution/providers.py +3 -3
  29. ansible/galaxy/role.py +2 -2
  30. ansible/inventory/group.py +6 -1
  31. ansible/inventory/host.py +6 -1
  32. ansible/module_utils/_internal/__init__.py +7 -4
  33. ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
  34. ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
  35. ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
  36. ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
  37. ansible/module_utils/_internal/{_ansiballz.py → _ansiballz/_loader.py} +10 -38
  38. ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
  39. ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
  40. ansible/module_utils/_internal/_datatag/__init__.py +23 -1
  41. ansible/module_utils/_internal/_deprecator.py +39 -34
  42. ansible/module_utils/_internal/_json/_profiles/__init__.py +1 -0
  43. ansible/module_utils/_internal/_messages.py +26 -2
  44. ansible/module_utils/_internal/_plugin_info.py +14 -1
  45. ansible/module_utils/ansible_release.py +1 -1
  46. ansible/module_utils/basic.py +58 -70
  47. ansible/module_utils/common/respawn.py +4 -41
  48. ansible/module_utils/common/yaml.py +1 -1
  49. ansible/module_utils/connection.py +8 -11
  50. ansible/module_utils/csharp/Ansible.Basic.cs +1 -1
  51. ansible/module_utils/csharp/Ansible.Privilege.cs +2 -2
  52. ansible/module_utils/facts/hardware/base.py +1 -1
  53. ansible/module_utils/facts/hardware/linux.py +1 -1
  54. ansible/module_utils/facts/other/facter.py +1 -1
  55. ansible/module_utils/facts/sysctl.py +4 -6
  56. ansible/module_utils/facts/system/caps.py +2 -2
  57. ansible/module_utils/facts/system/distribution.py +2 -2
  58. ansible/module_utils/facts/system/local.py +1 -1
  59. ansible/module_utils/facts/virtual/linux.py +1 -1
  60. ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 +1 -1
  61. ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1 +1 -1
  62. ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 +1 -1
  63. ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1 +1 -1
  64. ansible/module_utils/service.py +1 -1
  65. ansible/module_utils/urls.py +5 -5
  66. ansible/modules/apt.py +9 -3
  67. ansible/modules/apt_repository.py +10 -10
  68. ansible/modules/assemble.py +7 -5
  69. ansible/modules/async_wrapper.py +7 -17
  70. ansible/modules/command.py +3 -3
  71. ansible/modules/copy.py +4 -4
  72. ansible/modules/cron.py +1 -1
  73. ansible/modules/expect.py +5 -5
  74. ansible/modules/file.py +16 -17
  75. ansible/modules/find.py +3 -3
  76. ansible/modules/get_url.py +17 -0
  77. ansible/modules/git.py +9 -7
  78. ansible/modules/hostname.py +2 -2
  79. ansible/modules/known_hosts.py +12 -14
  80. ansible/modules/package.py +6 -0
  81. ansible/modules/pip.py +9 -11
  82. ansible/modules/raw.py +2 -2
  83. ansible/modules/replace.py +2 -2
  84. ansible/modules/slurp.py +10 -13
  85. ansible/modules/stat.py +6 -8
  86. ansible/modules/unarchive.py +6 -6
  87. ansible/modules/user.py +1 -1
  88. ansible/modules/wait_for.py +38 -33
  89. ansible/modules/yum_repository.py +4 -3
  90. ansible/parsing/dataloader.py +2 -2
  91. ansible/parsing/mod_args.py +38 -20
  92. ansible/parsing/vault/__init__.py +9 -13
  93. ansible/playbook/base.py +7 -4
  94. ansible/playbook/helpers.py +1 -1
  95. ansible/playbook/included_file.py +3 -1
  96. ansible/playbook/play_context.py +2 -0
  97. ansible/playbook/playbook_include.py +23 -56
  98. ansible/playbook/role/__init__.py +38 -21
  99. ansible/playbook/taggable.py +19 -5
  100. ansible/playbook/task.py +2 -0
  101. ansible/plugins/action/__init__.py +2 -2
  102. ansible/plugins/action/assemble.py +2 -1
  103. ansible/plugins/action/assert.py +2 -2
  104. ansible/plugins/action/fetch.py +3 -3
  105. ansible/plugins/action/script.py +5 -4
  106. ansible/plugins/action/template.py +9 -3
  107. ansible/plugins/cache/__init__.py +17 -19
  108. ansible/plugins/callback/__init__.py +77 -87
  109. ansible/plugins/callback/default.py +0 -3
  110. ansible/plugins/callback/junit.py +0 -6
  111. ansible/plugins/callback/tree.py +5 -5
  112. ansible/plugins/connection/local.py +4 -4
  113. ansible/plugins/connection/paramiko_ssh.py +5 -5
  114. ansible/plugins/connection/ssh.py +9 -7
  115. ansible/plugins/connection/winrm.py +1 -1
  116. ansible/plugins/filter/core.py +19 -21
  117. ansible/plugins/filter/encryption.py +10 -2
  118. ansible/plugins/filter/pow.yml +1 -1
  119. ansible/plugins/filter/root.yml +1 -1
  120. ansible/plugins/filter/strftime.yml +3 -3
  121. ansible/plugins/filter/to_uuid.yml +1 -1
  122. ansible/plugins/inventory/script.py +1 -1
  123. ansible/plugins/list.py +5 -4
  124. ansible/plugins/loader.py +5 -0
  125. ansible/plugins/lookup/password.py +4 -6
  126. ansible/plugins/lookup/template.py +9 -4
  127. ansible/plugins/shell/powershell.py +3 -2
  128. ansible/plugins/shell/sh.py +3 -2
  129. ansible/plugins/strategy/__init__.py +3 -3
  130. ansible/plugins/test/core.py +2 -2
  131. ansible/release.py +1 -1
  132. ansible/template/__init__.py +9 -53
  133. ansible/utils/collection_loader/_collection_finder.py +3 -3
  134. ansible/utils/display.py +38 -37
  135. ansible/utils/galaxy.py +2 -2
  136. ansible/utils/hashing.py +6 -7
  137. ansible/utils/path.py +6 -8
  138. ansible/utils/py3compat.py +2 -1
  139. ansible/utils/ssh_functions.py +3 -2
  140. ansible/utils/vars.py +4 -1
  141. ansible/vars/manager.py +6 -3
  142. ansible/vars/plugins.py +3 -3
  143. ansible/vars/reserved.py +6 -4
  144. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/METADATA +1 -1
  145. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/RECORD +184 -173
  146. ansible_test/_internal/__init__.py +5 -0
  147. ansible_test/_internal/ansible_util.py +1 -1
  148. ansible_test/_internal/classification/python.py +6 -0
  149. ansible_test/_internal/cli/commands/__init__.py +0 -5
  150. ansible_test/_internal/cli/environments.py +51 -5
  151. ansible_test/_internal/commands/coverage/__init__.py +1 -1
  152. ansible_test/_internal/commands/integration/__init__.py +18 -5
  153. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  154. ansible_test/_internal/commands/integration/coverage.py +7 -2
  155. ansible_test/_internal/commands/sanity/__init__.py +3 -1
  156. ansible_test/_internal/commands/sanity/integration_aliases.py +11 -0
  157. ansible_test/_internal/commands/shell/__init__.py +43 -4
  158. ansible_test/_internal/commands/units/__init__.py +4 -1
  159. ansible_test/_internal/config.py +21 -13
  160. ansible_test/_internal/debugging.py +166 -0
  161. ansible_test/_internal/delegation.py +21 -13
  162. ansible_test/_internal/host_profiles.py +259 -16
  163. ansible_test/_internal/inventory.py +4 -0
  164. ansible_test/_internal/metadata.py +94 -4
  165. ansible_test/_internal/processes.py +80 -0
  166. ansible_test/_internal/provisioning.py +10 -4
  167. ansible_test/_internal/python_requirements.py +27 -0
  168. ansible_test/_internal/ssh.py +1 -5
  169. ansible_test/_internal/target.py +8 -0
  170. ansible_test/_internal/thread.py +2 -1
  171. ansible_test/_internal/timeout.py +1 -1
  172. ansible_test/_internal/util.py +20 -12
  173. ansible_test/_internal/util_common.py +13 -3
  174. ansible_test/_util/target/injector/python.py +8 -0
  175. ansible_test/_util/target/setup/requirements.py +3 -9
  176. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/WHEEL +0 -0
  177. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/entry_points.txt +0 -0
  178. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/COPYING +0 -0
  179. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  180. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  181. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  182. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  183. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  184. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@ DOCUMENTATION:
3
3
  version_added: "1.9"
4
4
  short_description: root of (math operation)
5
5
  description:
6
- - Math operation that returns the Nth root of inputed number C(X ^^ N).
6
+ - Math operation that returns the Nth root of inputted number C(X ^^ N).
7
7
  positional: _input, base
8
8
  options:
9
9
  _input:
@@ -1,16 +1,16 @@
1
1
  DOCUMENTATION:
2
2
  name: strftime
3
3
  version_added: "2.4"
4
- short_description: date formating
4
+ short_description: date formatting
5
5
  description:
6
- - Using Python's C(strftime) function, take a data formating string and a date/time to create a formatted date.
6
+ - Using Python's C(strftime) function, take a data formatting string and a date/time to create a formatted date.
7
7
  notes:
8
8
  - This is a passthrough to Python's C(stftime), for a complete set of formatting options go to https://strftime.org/.
9
9
  positional: _input, second, utc
10
10
  options:
11
11
  _input:
12
12
  description:
13
- - A formating string following C(stftime) conventions.
13
+ - A formatting string following C(stftime) conventions.
14
14
  - See L(the Python documentation, https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior) for a reference.
15
15
  type: str
16
16
  required: true
@@ -7,7 +7,7 @@ DOCUMENTATION:
7
7
  positional: _input, namespace
8
8
  options:
9
9
  _input:
10
- description: String to use as base fo the UUID.
10
+ description: String to use as base of the UUID.
11
11
  type: str
12
12
  required: true
13
13
  namespace:
@@ -98,7 +98,7 @@ EXAMPLES = r'''# fmt: code
98
98
  def get_api_data(namespace: str, pretty=False) -> str:
99
99
  """
100
100
  :param namespace: parameter for our custom api
101
- :param pretty: Human redable JSON vs machine readable
101
+ :param pretty: Human readable JSON vs machine readable
102
102
  :return: JSON string
103
103
  """
104
104
  found_data = list(MyInventoryAPI(namespace))
ansible/plugins/list.py CHANGED
@@ -58,7 +58,7 @@ def get_composite_name(collection, name, path, depth):
58
58
  return '.'.join(composite)
59
59
 
60
60
 
61
- def _list_plugins_from_paths(ptype, dirs, collection, depth=0):
61
+ def _list_plugins_from_paths(ptype, dirs, collection, depth=0, docs=False):
62
62
  # TODO: update to use importlib.resources
63
63
 
64
64
  plugins = {}
@@ -93,14 +93,15 @@ def _list_plugins_from_paths(ptype, dirs, collection, depth=0):
93
93
  continue
94
94
 
95
95
  # actually recurse dirs
96
- plugins.update(_list_plugins_from_paths(ptype, [to_native(full_path)], collection, depth=depth + 1))
96
+ plugins.update(_list_plugins_from_paths(ptype, [to_native(full_path)], collection, depth=depth + 1, docs=docs))
97
97
  else:
98
98
  if any([
99
99
  plugin in C.IGNORE_FILES, # general files to ignore
100
100
  to_native(b_ext) in C.REJECT_EXTS, # general extensions to ignore
101
- b_ext in (b'.yml', b'.yaml', b'.json'), # ignore docs files TODO: constant!
101
+ b_ext in (b'.yml', b'.yaml', b'.json'), # ignore docs files
102
102
  plugin in IGNORE.get(bkey, ()), # plugin in reject list
103
103
  os.path.islink(full_path), # skip aliases, author should document in 'aliases' field
104
+ not docs and b_ext in (b''), # ignore no ext when looking for docs files
104
105
  ]):
105
106
  continue
106
107
 
@@ -179,7 +180,7 @@ def _list_collection_plugins_with_info(
179
180
 
180
181
  # raise Exception('bad acr for %s, %s' % (collection, ptype))
181
182
 
182
- plugin_paths.update(_list_plugins_from_paths(ptype, dirs, collection))
183
+ plugin_paths.update(_list_plugins_from_paths(ptype, dirs, collection, docs=True))
183
184
 
184
185
  plugins = {}
185
186
  if ptype in ('module',):
ansible/plugins/loader.py CHANGED
@@ -989,6 +989,9 @@ class PluginLoader:
989
989
  def get_with_context(self, name, *args, **kwargs) -> get_with_context_result:
990
990
  """ instantiates a plugin of the given name using arguments """
991
991
 
992
+ if not name:
993
+ raise ValueError('A non-empty plugin name is required.')
994
+
992
995
  found_in_cache = True
993
996
  class_only = kwargs.pop('class_only', False)
994
997
  collection_list = kwargs.pop('collection_list', None)
@@ -1034,6 +1037,7 @@ class PluginLoader:
1034
1037
  except AttributeError:
1035
1038
  return get_with_context_result(None, plugin_load_context)
1036
1039
  if not issubclass(obj, plugin_class):
1040
+ display.warning(f"Ignoring {self.type} plugin {resolved_type_name!r} due to missing base class {self.base_class!r}.")
1037
1041
  return get_with_context_result(None, plugin_load_context)
1038
1042
 
1039
1043
  # FIXME: update this to use the load context
@@ -1721,6 +1725,7 @@ callback_loader = PluginLoader(
1721
1725
  'ansible.plugins.callback',
1722
1726
  C.DEFAULT_CALLBACK_PLUGIN_PATH,
1723
1727
  'callback_plugins',
1728
+ required_base_class='CallbackBase',
1724
1729
  )
1725
1730
 
1726
1731
  connection_loader = PluginLoader(
@@ -126,6 +126,7 @@ _raw:
126
126
  elements: str
127
127
  """
128
128
 
129
+ import contextlib
129
130
  import os
130
131
  import string
131
132
  import time
@@ -269,15 +270,12 @@ def _get_lock(b_path):
269
270
  b_pathdir = os.path.dirname(b_path)
270
271
  lockfile_name = to_bytes("%s.ansible_lockfile" % hashlib.sha1(b_path).hexdigest())
271
272
  lockfile = os.path.join(b_pathdir, lockfile_name)
272
- if not os.path.exists(lockfile) and b_path != to_bytes('/dev/null'):
273
- try:
274
- makedirs_safe(b_pathdir, mode=0o700)
273
+ if b_path != b'/dev/null':
274
+ makedirs_safe(b_pathdir, mode=0o700)
275
+ with contextlib.suppress(FileExistsError):
275
276
  fd = os.open(lockfile, os.O_CREAT | os.O_EXCL)
276
277
  os.close(fd)
277
278
  first_process = True
278
- except OSError as e:
279
- if e.strerror != 'File exists':
280
- raise
281
279
 
282
280
  counter = 0
283
281
  # if the lock is got by other process, wait until it's released
@@ -22,7 +22,7 @@ DOCUMENTATION = """
22
22
  default: true
23
23
  deprecated:
24
24
  why: This option is no longer used in the Ansible Core code base.
25
- version: "2.21"
25
+ version: "2.23"
26
26
  alternatives: Jinja2 native mode is now the default and only option, which is mutually exclusive with this option.
27
27
  variable_start_string:
28
28
  description: The string marking the beginning of a print statement.
@@ -45,7 +45,7 @@ DOCUMENTATION = """
45
45
  type: bool
46
46
  deprecated:
47
47
  why: This option is no longer used in the Ansible Core code base.
48
- version: "2.21"
48
+ version: "2.23"
49
49
  alternatives: Jinja2 native mode is now the default and only option.
50
50
  template_vars:
51
51
  description: A dictionary, the keys become additional variables available for templating.
@@ -105,7 +105,8 @@ import os
105
105
 
106
106
  from ansible.errors import AnsibleError
107
107
  from ansible.plugins.lookup import LookupBase
108
- from ansible.template import generate_ansible_template_vars, trust_as_template
108
+ from ansible.template import trust_as_template
109
+ from ansible._internal._templating import _template_vars
109
110
  from ansible.utils.display import Display
110
111
 
111
112
 
@@ -157,7 +158,11 @@ class LookupModule(LookupBase):
157
158
  # argument.
158
159
  # FIXME: why isn't this a chainmap with a sacrificial bottom layer?
159
160
  vars = deepcopy(variables)
160
- vars.update(generate_ansible_template_vars(term, fullpath=lookupfile))
161
+ vars.update(_template_vars.generate_ansible_template_vars(
162
+ path=term,
163
+ fullpath=lookupfile,
164
+ include_ansible_managed='ansible_managed' not in vars, # do not clobber ansible_managed when set by the user
165
+ ))
161
166
  vars.update(lookup_template_vars)
162
167
 
163
168
  overrides = dict(
@@ -325,8 +325,9 @@ class ShellModule(ShellBase):
325
325
 
326
326
  def checksum(self, path, *args, **kwargs):
327
327
  display.deprecated(
328
- "The 'ShellModule.checksum' method is deprecated. Use 'ActionBase._execute_remote_stat()' instead.",
329
- version="2.23"
328
+ msg="The `ShellModule.checksum` method is deprecated.",
329
+ version="2.23",
330
+ help_text="Use `ActionBase._execute_remote_stat()` instead.",
330
331
  )
331
332
  path = self._escape(self._unquote(path))
332
333
  script = """
@@ -48,8 +48,9 @@ class ShellModule(ShellBase):
48
48
 
49
49
  def checksum(self, path, python_interp):
50
50
  display.deprecated(
51
- "The 'ShellModule.checksum' method is deprecated. Use 'ActionBase._execute_remote_stat()' instead.",
52
- version="2.23"
51
+ msg="The `ShellModule.checksum` method is deprecated.",
52
+ version="2.23",
53
+ help_text="Use `ActionBase._execute_remote_stat()` instead.",
53
54
  )
54
55
  # In the following test, each condition is a check and logical
55
56
  # comparison (|| or &&) that sets the rc value. Every check is run so
@@ -128,7 +128,7 @@ def results_thread_main(strategy: StrategyBase) -> None:
128
128
  strategy._workers[result.worker_id].worker_queue.put(value)
129
129
  else:
130
130
  display.warning('Received an invalid object (%s) in the result queue: %r' % (type(result), result))
131
- except (IOError, EOFError):
131
+ except (OSError, EOFError):
132
132
  break
133
133
  except queue.Empty:
134
134
  pass
@@ -402,9 +402,9 @@ class StrategyBase:
402
402
  time.sleep(0.0001)
403
403
 
404
404
  self._pending_results += 1
405
- except (EOFError, IOError, AssertionError) as e:
405
+ except (EOFError, OSError, AssertionError) as ex:
406
406
  # most likely an abort
407
- display.debug("got an error while queuing: %s" % e)
407
+ display.debug(f"got an error while queuing: {ex}")
408
408
  return
409
409
  display.debug("exiting _queue_task() for %s/%s" % (host.name, task.action))
410
410
 
@@ -175,8 +175,8 @@ def vaulted_file(value):
175
175
  try:
176
176
  with open(to_bytes(value), 'rb') as f:
177
177
  return is_encrypted_file(f)
178
- except (OSError, IOError) as e:
179
- raise errors.AnsibleFilterError(f"Cannot test if the file {value} is a vault.") from e
178
+ except OSError as ex:
179
+ raise errors.AnsibleFilterError(f"Cannot test if the file {value!r} is a vault.") from ex
180
180
 
181
181
 
182
182
  def match(value, pattern='', ignorecase=False, multiline=False):
ansible/release.py CHANGED
@@ -17,6 +17,6 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = '2.19.0b5'
20
+ __version__ = '2.19.0b7'
21
21
  __author__ = 'Ansible, Inc.'
22
22
  __codename__ = "What Is and What Should Never Be"
@@ -1,22 +1,18 @@
1
1
  from __future__ import annotations as _annotations
2
2
 
3
3
  import contextlib as _contextlib
4
- import datetime as _datetime
5
4
  import io as _io
6
5
  import os as _os
7
- import pwd as _pwd
8
- import time as _time
9
6
  import typing as _t
10
7
 
11
8
  from jinja2 import environment as _environment
12
9
 
13
10
  from ansible import _internal
14
- from ansible import constants as _constants
15
11
  from ansible import errors as _errors
16
12
  from ansible._internal._datatag import _tags, _wrappers
17
- from ansible._internal._templating import _jinja_bits, _engine, _jinja_common
13
+ from ansible._internal._templating import _jinja_bits, _engine, _jinja_common, _template_vars
14
+
18
15
  from ansible.module_utils import datatag as _module_utils_datatag
19
- from ansible.module_utils._internal import _datatag
20
16
  from ansible.utils.display import Display as _Display
21
17
 
22
18
  if _t.TYPE_CHECKING: # pragma: nocover
@@ -352,57 +348,17 @@ class Templar:
352
348
  )
353
349
 
354
350
 
355
- def generate_ansible_template_vars(path: str, fullpath: str | None = None, dest_path: str | None = None) -> dict[str, object]:
351
+ def generate_ansible_template_vars(
352
+ path: str,
353
+ fullpath: str | None = None,
354
+ dest_path: str | None = None,
355
+ ) -> dict[str, object]:
356
356
  """
357
357
  Generate and return a dictionary with variable metadata about the template specified by `fullpath`.
358
358
  If `fullpath` is `None`, `path` will be used instead.
359
359
  """
360
- if fullpath is None:
361
- fullpath = _os.path.abspath(path)
362
-
363
- template_path = fullpath
364
- template_stat = _os.stat(template_path)
365
-
366
- template_uid: int | str
367
-
368
- try:
369
- template_uid = _pwd.getpwuid(template_stat.st_uid).pw_name
370
- except KeyError:
371
- template_uid = template_stat.st_uid
372
-
373
- managed_default = _constants.config.get_config_value('DEFAULT_MANAGED_STR')
374
-
375
- managed_str = managed_default.format(
376
- # IMPORTANT: These values must be constant strings to avoid template injection.
377
- # Use Jinja template expressions where variables are needed.
378
- host="{{ template_host }}",
379
- uid="{{ template_uid }}",
380
- file="{{ template_path }}",
381
- )
382
-
383
- ansible_managed = _time.strftime(managed_str, _time.localtime(template_stat.st_mtime))
384
- # DTFIX7: this should not be tag_copy, it should either be an origin copy or some kind of derived origin
385
- ansible_managed = _datatag.AnsibleTagHelper.tag_copy(managed_default, ansible_managed)
386
- ansible_managed = trust_as_template(ansible_managed)
387
- ansible_managed = _module_utils_datatag.deprecate_value(
388
- value=ansible_managed,
389
- msg="The `ansible_managed` variable is deprecated.",
390
- help_text="Define and use a custom variable instead.",
391
- version='2.23',
392
- )
393
-
394
- temp_vars = dict(
395
- template_host=_os.uname()[1],
396
- template_path=path,
397
- template_mtime=_datetime.datetime.fromtimestamp(template_stat.st_mtime),
398
- template_uid=template_uid,
399
- template_run_date=_datetime.datetime.now(),
400
- template_destpath=dest_path,
401
- template_fullpath=fullpath,
402
- ansible_managed=ansible_managed,
403
- )
404
-
405
- return temp_vars
360
+ # deprecated description="deprecate `generate_ansible_template_vars`, collections should inline the necessary variables" core_version="2.23"
361
+ return _template_vars.generate_ansible_template_vars(path=path, fullpath=fullpath, dest_path=dest_path, include_ansible_managed=True)
406
362
 
407
363
 
408
364
  def trust_as_template(value: _TTrustable) -> _TTrustable:
@@ -1095,8 +1095,8 @@ def _get_collection_playbook_path(playbook):
1095
1095
  try:
1096
1096
  # get_collection_path
1097
1097
  pkg = import_module(acr.n_python_collection_package_name)
1098
- except (IOError, ModuleNotFoundError) as e:
1099
- # leaving e as debug target, even though not used in normal code
1098
+ except (OSError, ModuleNotFoundError) as ex:
1099
+ # leaving ex as debug target, even though not used in normal code
1100
1100
  pkg = None
1101
1101
 
1102
1102
  if pkg:
@@ -1151,7 +1151,7 @@ def _get_collection_resource_path(name, ref_type, collection_list=None):
1151
1151
  path = os.path.dirname(_to_bytes(sys.modules[acr.n_python_package_name].__file__))
1152
1152
  return resource, _to_text(path), collection_name
1153
1153
 
1154
- except (IOError, ModuleNotFoundError) as e:
1154
+ except (OSError, ModuleNotFoundError) as ex:
1155
1155
  continue
1156
1156
  except Exception as ex:
1157
1157
  # FIXME: pick out typical import errors first, then error logging
ansible/utils/display.py CHANGED
@@ -17,6 +17,7 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
+ import contextlib
20
21
  import dataclasses
21
22
 
22
23
  try:
@@ -39,7 +40,6 @@ import secrets
39
40
  import subprocess
40
41
  import sys
41
42
  import termios
42
- import textwrap
43
43
  import threading
44
44
  import time
45
45
  import tty
@@ -216,10 +216,22 @@ b_COW_PATHS = (
216
216
 
217
217
 
218
218
  def _synchronize_textiowrapper(tio: t.TextIO, lock: threading.RLock):
219
- # Ensure that a background thread can't hold the internal buffer lock on a file object
220
- # during a fork, which causes forked children to hang. We're using display's existing lock for
221
- # convenience (and entering the lock before a fork).
219
+ """
220
+ This decorator ensures that the supplied RLock is held before invoking the wrapped methods.
221
+ It is intended to prevent background threads from holding the Python stdout/stderr buffer lock on a file object during a fork.
222
+ Since background threads are abandoned in child forks, locks they hold are orphaned in a locked state.
223
+ Attempts to acquire an orphaned lock in this state will block forever, effectively hanging the child process on stdout/stderr writes.
224
+ The shared lock is permanently disabled immediately after a fork.
225
+ This prevents hangs in early post-fork code (e.g., stdio writes from pydevd, coverage, etc.) before user code has resumed and released the lock.
226
+ """
227
+
222
228
  def _wrap_with_lock(f, lock):
229
+ def disable_lock():
230
+ nonlocal lock
231
+ lock = contextlib.nullcontext()
232
+
233
+ os.register_at_fork(after_in_child=disable_lock)
234
+
223
235
  @wraps(f)
224
236
  def locking_wrapper(*args, **kwargs):
225
237
  with lock:
@@ -316,7 +328,6 @@ class Display(metaclass=Singleton):
316
328
  self.noncow = C.ANSIBLE_COW_SELECTION
317
329
 
318
330
  self.set_cowsay_info()
319
- self._wrap_stderr = C.WRAP_STDERR
320
331
 
321
332
  if self.b_cowsay:
322
333
  try:
@@ -474,7 +485,7 @@ class Display(metaclass=Singleton):
474
485
  # final flush at shutdown.
475
486
  # try:
476
487
  # fileobj.flush()
477
- # except IOError as e:
488
+ # except OSError as e:
478
489
  # # Ignore EPIPE in case fileobj has been prematurely closed, eg.
479
490
  # # when piping to "head -n1"
480
491
  # if e.errno != errno.EPIPE:
@@ -603,20 +614,24 @@ class Display(metaclass=Singleton):
603
614
  else:
604
615
  removal_fragment = 'This feature will be removed'
605
616
 
606
- if not deprecator or deprecator.type == _deprecator.INDETERMINATE_DEPRECATOR.type:
607
- collection = None
617
+ if not deprecator or not deprecator.type:
618
+ # indeterminate has no resolved_name or type
619
+ # collections have a resolved_name but no type
620
+ collection = deprecator.resolved_name if deprecator else None
608
621
  plugin_fragment = ''
609
- elif deprecator.type == _deprecator._COLLECTION_ONLY_TYPE:
622
+ elif deprecator.resolved_name == 'ansible.builtin':
623
+ # core deprecations from base classes (the API) have no plugin name, only 'ansible.builtin'
624
+ plugin_type_name = str(deprecator.type) if deprecator.type is _messages.PluginType.MODULE else f'{deprecator.type} plugin'
625
+
610
626
  collection = deprecator.resolved_name
611
- plugin_fragment = ''
627
+ plugin_fragment = f'the {plugin_type_name} API'
612
628
  else:
613
629
  parts = deprecator.resolved_name.split('.')
614
630
  plugin_name = parts[-1]
615
- # DTFIX1: normalize 'modules' -> 'module' before storing it so we can eliminate the normalization here
616
- plugin_type = "module" if deprecator.type in ("module", "modules") else f'{deprecator.type} plugin'
631
+ plugin_type_name = str(deprecator.type) if deprecator.type is _messages.PluginType.MODULE else f'{deprecator.type} plugin'
617
632
 
618
633
  collection = '.'.join(parts[:2]) if len(parts) > 2 else None
619
- plugin_fragment = f'{plugin_type} {plugin_name!r}'
634
+ plugin_fragment = f'{plugin_type_name} {plugin_name!r}'
620
635
 
621
636
  if collection and plugin_fragment:
622
637
  plugin_fragment += ' in'
@@ -646,13 +661,6 @@ class Display(metaclass=Singleton):
646
661
 
647
662
  return _join_sentences(msg, deprecation_msg)
648
663
 
649
- def _wrap_message(self, msg: str, wrap_text: bool) -> str:
650
- if wrap_text and self._wrap_stderr:
651
- wrapped = textwrap.wrap(msg, self.columns, drop_whitespace=False)
652
- msg = "\n".join(wrapped) + "\n"
653
-
654
- return msg
655
-
656
664
  @staticmethod
657
665
  def _deduplicate(msg: str, messages: set[str]) -> bool:
658
666
  """
@@ -769,9 +777,6 @@ class Display(metaclass=Singleton):
769
777
  msg = _format_message(warning, _traceback.is_traceback_enabled(_traceback.TracebackEvent.DEPRECATED))
770
778
  msg = f'[DEPRECATION WARNING]: {msg}'
771
779
 
772
- # DTFIX3: what should we do with wrap_message?
773
- msg = self._wrap_message(msg=msg, wrap_text=True)
774
-
775
780
  if self._deduplicate(msg, self._deprecations):
776
781
  return
777
782
 
@@ -788,6 +793,8 @@ class Display(metaclass=Singleton):
788
793
  """Display a warning message."""
789
794
  _skip_stackwalk = True
790
795
 
796
+ # deprecated: description='The formatted argument has no effect.' core_version='2.23'
797
+
791
798
  # This is the pre-proxy half of the `warning` implementation.
792
799
  # Any logic that must occur on workers needs to be implemented here.
793
800
 
@@ -807,13 +814,12 @@ class Display(metaclass=Singleton):
807
814
 
808
815
  if warning_ctx := _DeferredWarningContext.current(optional=True):
809
816
  warning_ctx.capture(warning)
810
- # DTFIX3: what to do about propagating wrap_text?
811
817
  return
812
818
 
813
- self._warning(warning, wrap_text=not formatted)
819
+ self._warning(warning)
814
820
 
815
821
  @_proxy
816
- def _warning(self, warning: _messages.WarningSummary, wrap_text: bool) -> None:
822
+ def _warning(self, warning: _messages.WarningSummary) -> None:
817
823
  """Internal implementation detail, use `warning` instead."""
818
824
 
819
825
  # This is the post-proxy half of the `warning` implementation.
@@ -825,9 +831,6 @@ class Display(metaclass=Singleton):
825
831
  if self._deduplicate(msg, self._warns):
826
832
  return
827
833
 
828
- # DTFIX3: what should we do with wrap_message?
829
- msg = self._wrap_message(msg=msg, wrap_text=wrap_text)
830
-
831
834
  self.display(msg, color=C.config.get_config_value('COLOR_WARN'), stderr=True, caplevel=-2)
832
835
 
833
836
  @_proxy
@@ -916,19 +919,20 @@ class Display(metaclass=Singleton):
916
919
  warning_ctx.capture(warning)
917
920
  return
918
921
 
919
- self._warning(warning, wrap_text=False)
922
+ self._warning(warning)
920
923
 
921
924
  def error(self, msg: str | BaseException, wrap_text: bool = True, stderr: bool = True) -> None:
922
925
  """Display an error message."""
923
926
  _skip_stackwalk = True
924
927
 
928
+ # deprecated: description='The wrap_text argument has no effect.' core_version='2.23'
929
+ # deprecated: description='The stderr argument has no effect.' core_version='2.23'
930
+
925
931
  # This is the pre-proxy half of the `error` implementation.
926
932
  # Any logic that must occur on workers needs to be implemented here.
927
933
 
928
934
  if isinstance(msg, BaseException):
929
935
  event = _error_factory.ControllerEventFactory.from_exception(msg, _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR))
930
-
931
- wrap_text = False
932
936
  else:
933
937
  event = _messages.Event(
934
938
  msg=msg,
@@ -939,10 +943,10 @@ class Display(metaclass=Singleton):
939
943
  event=event,
940
944
  )
941
945
 
942
- self._error(error, wrap_text=wrap_text, stderr=stderr)
946
+ self._error(error, stderr=True)
943
947
 
944
948
  @_proxy
945
- def _error(self, error: _messages.ErrorSummary, wrap_text: bool, stderr: bool) -> None:
949
+ def _error(self, error: _messages.ErrorSummary, stderr: bool) -> None:
946
950
  """Internal implementation detail, use `error` instead."""
947
951
 
948
952
  # This is the post-proxy half of the `error` implementation.
@@ -954,9 +958,6 @@ class Display(metaclass=Singleton):
954
958
  if self._deduplicate(msg, self._errors):
955
959
  return
956
960
 
957
- # DTFIX3: what should we do with wrap_message?
958
- msg = self._wrap_message(msg=msg, wrap_text=wrap_text)
959
-
960
961
  self.display(msg, color=C.config.get_config_value('COLOR_ERROR'), stderr=stderr, caplevel=-1)
961
962
 
962
963
  @staticmethod
ansible/utils/galaxy.py CHANGED
@@ -57,8 +57,8 @@ def scm_archive_resource(src, scm='git', name=None, version='HEAD', keep_scm_met
57
57
 
58
58
  try:
59
59
  scm_path = get_bin_path(scm)
60
- except (ValueError, OSError, IOError):
61
- raise AnsibleError("could not find/use %s, it is required to continue with installing %s" % (scm, src))
60
+ except (ValueError, OSError) as ex:
61
+ raise AnsibleError(f"Could not find/use {scm!r}, it is required to continue with installing {src!r}.") from ex
62
62
 
63
63
  tempdir = tempfile.mkdtemp(dir=C.DEFAULT_LOCAL_TMP)
64
64
  clone_cmd = [scm_path, 'clone']
ansible/utils/hashing.py CHANGED
@@ -48,14 +48,13 @@ def secure_hash(filename, hash_func=sha1):
48
48
  digest = hash_func()
49
49
  blocksize = 64 * 1024
50
50
  try:
51
- infile = open(to_bytes(filename, errors='surrogate_or_strict'), 'rb')
52
- block = infile.read(blocksize)
53
- while block:
54
- digest.update(block)
51
+ with open(filename, 'rb') as infile:
55
52
  block = infile.read(blocksize)
56
- infile.close()
57
- except IOError as e:
58
- raise AnsibleError("error while accessing the file %s, error was: %s" % (filename, e))
53
+ while block:
54
+ digest.update(block)
55
+ block = infile.read(blocksize)
56
+ except OSError as ex:
57
+ raise AnsibleError(f"Error while accessing the file {filename!r}.") from ex
59
58
  return digest.hexdigest()
60
59
 
61
60
 
ansible/utils/path.py CHANGED
@@ -19,9 +19,8 @@ from __future__ import annotations
19
19
  import os
20
20
  import shutil
21
21
 
22
- from errno import EEXIST
23
22
  from ansible.errors import AnsibleError
24
- from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
23
+ from ansible.module_utils.common.text.converters import to_bytes, to_text
25
24
 
26
25
 
27
26
  __all__ = ['unfrackpath', 'makedirs_safe']
@@ -84,12 +83,11 @@ def makedirs_safe(path, mode=None):
84
83
  if not os.path.exists(b_rpath):
85
84
  try:
86
85
  if mode:
87
- os.makedirs(b_rpath, mode)
86
+ os.makedirs(b_rpath, mode, exist_ok=True)
88
87
  else:
89
- os.makedirs(b_rpath)
90
- except OSError as e:
91
- if e.errno != EEXIST:
92
- raise AnsibleError("Unable to create local directories(%s): %s" % (to_native(rpath), to_native(e)))
88
+ os.makedirs(b_rpath, exist_ok=True)
89
+ except OSError as ex:
90
+ raise AnsibleError(f"Unable to create local directories {rpath!r}.") from ex
93
91
 
94
92
 
95
93
  def basedir(source):
@@ -104,7 +102,7 @@ def basedir(source):
104
102
  dname = os.path.dirname(source)
105
103
 
106
104
  if dname:
107
- # don't follow symlinks for basedir, enables source re-use
105
+ # don't follow symlinks for basedir, enables source reuse
108
106
  dname = os.path.abspath(dname)
109
107
 
110
108
  return to_text(dname, errors='surrogate_or_strict')
@@ -19,8 +19,9 @@ def __getattr__(name):
19
19
  raise AttributeError(name)
20
20
 
21
21
  display.deprecated(
22
- msg='ansible.utils.py3compat.environ is deprecated in favor of os.environ.',
22
+ msg='`ansible.utils.py3compat.environ` is deprecated.',
23
23
  version='2.20',
24
+ help_text='Use `os.environ` from the Python standard library instead.',
24
25
  )
25
26
 
26
27
  return os.environ
@@ -57,8 +57,9 @@ def set_default_transport():
57
57
  # deal with 'smart' connection .. one time ..
58
58
  if C.DEFAULT_TRANSPORT == 'smart':
59
59
  display.deprecated(
60
- msg="The 'smart' option for connections is deprecated. Set the connection plugin directly instead.",
61
- version='2.20',
60
+ msg="The `smart` option for connections is deprecated.",
61
+ version="2.20",
62
+ help_text="Set the connection plugin directly instead.",
62
63
  )
63
64
 
64
65
  # see if SSH can support ControlPersist if not use paramiko