ansible-core 2.19.0b5__py3-none-any.whl → 2.19.0b6__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 (117) 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 +7 -4
  5. ansible/_internal/_templating/_jinja_plugins.py +5 -2
  6. ansible/_internal/_templating/_template_vars.py +72 -0
  7. ansible/_internal/_templating/_transform.py +6 -0
  8. ansible/_internal/_yaml/_constructor.py +4 -4
  9. ansible/_internal/_yaml/_dumper.py +26 -18
  10. ansible/cli/__init__.py +7 -12
  11. ansible/cli/arguments/option_helpers.py +1 -1
  12. ansible/cli/console.py +1 -1
  13. ansible/cli/doc.py +2 -2
  14. ansible/cli/inventory.py +5 -7
  15. ansible/config/base.yml +24 -0
  16. ansible/errors/__init__.py +2 -1
  17. ansible/executor/module_common.py +67 -39
  18. ansible/executor/process/worker.py +2 -2
  19. ansible/galaxy/api.py +1 -4
  20. ansible/galaxy/collection/__init__.py +1 -6
  21. ansible/galaxy/collection/concrete_artifact_manager.py +2 -8
  22. ansible/galaxy/role.py +2 -2
  23. ansible/module_utils/_internal/__init__.py +7 -4
  24. ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
  25. ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
  26. ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
  27. ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
  28. ansible/module_utils/_internal/{_ansiballz.py → _ansiballz/_loader.py} +10 -38
  29. ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
  30. ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
  31. ansible/module_utils/_internal/_datatag/__init__.py +23 -1
  32. ansible/module_utils/_internal/_deprecator.py +27 -33
  33. ansible/module_utils/_internal/_json/_profiles/__init__.py +1 -0
  34. ansible/module_utils/_internal/_messages.py +26 -2
  35. ansible/module_utils/_internal/_plugin_info.py +14 -1
  36. ansible/module_utils/ansible_release.py +1 -1
  37. ansible/module_utils/basic.py +46 -56
  38. ansible/module_utils/common/respawn.py +4 -41
  39. ansible/module_utils/connection.py +8 -11
  40. ansible/module_utils/facts/hardware/linux.py +1 -1
  41. ansible/module_utils/facts/sysctl.py +4 -6
  42. ansible/module_utils/facts/system/caps.py +2 -2
  43. ansible/module_utils/facts/system/local.py +1 -1
  44. ansible/module_utils/facts/virtual/linux.py +1 -1
  45. ansible/module_utils/service.py +1 -1
  46. ansible/module_utils/urls.py +4 -4
  47. ansible/modules/apt_repository.py +10 -10
  48. ansible/modules/assemble.py +2 -2
  49. ansible/modules/async_wrapper.py +7 -17
  50. ansible/modules/command.py +3 -3
  51. ansible/modules/copy.py +4 -4
  52. ansible/modules/cron.py +1 -1
  53. ansible/modules/file.py +16 -17
  54. ansible/modules/find.py +3 -3
  55. ansible/modules/get_url.py +17 -0
  56. ansible/modules/git.py +9 -7
  57. ansible/modules/known_hosts.py +12 -14
  58. ansible/modules/package.py +6 -0
  59. ansible/modules/replace.py +2 -2
  60. ansible/modules/slurp.py +10 -13
  61. ansible/modules/stat.py +5 -7
  62. ansible/modules/unarchive.py +6 -6
  63. ansible/modules/user.py +1 -1
  64. ansible/modules/wait_for.py +28 -30
  65. ansible/modules/yum_repository.py +4 -3
  66. ansible/parsing/dataloader.py +2 -2
  67. ansible/parsing/vault/__init__.py +6 -10
  68. ansible/playbook/base.py +7 -2
  69. ansible/playbook/included_file.py +3 -1
  70. ansible/playbook/play_context.py +2 -0
  71. ansible/playbook/taggable.py +19 -5
  72. ansible/playbook/task.py +2 -0
  73. ansible/plugins/action/fetch.py +3 -3
  74. ansible/plugins/action/template.py +8 -2
  75. ansible/plugins/cache/__init__.py +17 -19
  76. ansible/plugins/callback/tree.py +5 -5
  77. ansible/plugins/connection/local.py +4 -4
  78. ansible/plugins/connection/paramiko_ssh.py +5 -5
  79. ansible/plugins/connection/ssh.py +8 -6
  80. ansible/plugins/connection/winrm.py +1 -1
  81. ansible/plugins/filter/core.py +19 -21
  82. ansible/plugins/filter/encryption.py +10 -2
  83. ansible/plugins/list.py +5 -4
  84. ansible/plugins/lookup/template.py +9 -4
  85. ansible/plugins/shell/powershell.py +3 -2
  86. ansible/plugins/shell/sh.py +3 -2
  87. ansible/plugins/strategy/__init__.py +3 -3
  88. ansible/plugins/test/core.py +2 -2
  89. ansible/release.py +1 -1
  90. ansible/template/__init__.py +9 -53
  91. ansible/utils/collection_loader/_collection_finder.py +3 -3
  92. ansible/utils/display.py +23 -12
  93. ansible/utils/galaxy.py +2 -2
  94. ansible/utils/hashing.py +6 -7
  95. ansible/utils/path.py +5 -7
  96. ansible/utils/py3compat.py +2 -1
  97. ansible/utils/ssh_functions.py +3 -2
  98. ansible/vars/plugins.py +3 -3
  99. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/METADATA +1 -1
  100. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/RECORD +117 -108
  101. ansible_test/_internal/commands/integration/coverage.py +7 -2
  102. ansible_test/_internal/host_profiles.py +62 -10
  103. ansible_test/_internal/provisioning.py +10 -4
  104. ansible_test/_internal/ssh.py +1 -5
  105. ansible_test/_internal/thread.py +2 -1
  106. ansible_test/_internal/timeout.py +1 -1
  107. ansible_test/_internal/util.py +20 -12
  108. ansible_test/_util/target/setup/requirements.py +3 -9
  109. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/WHEEL +0 -0
  110. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/entry_points.txt +0 -0
  111. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/COPYING +0 -0
  112. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  113. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  114. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  115. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  116. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  117. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/top_level.txt +0 -0
@@ -5,7 +5,7 @@ import pathlib
5
5
  import sys
6
6
  import typing as t
7
7
 
8
- from ansible.module_utils._internal import _stack, _messages, _validation
8
+ from ansible.module_utils._internal import _stack, _messages, _validation, _plugin_info
9
9
 
10
10
 
11
11
  def deprecator_from_collection_name(collection_name: str | None) -> _messages.PluginInfo | None:
@@ -19,7 +19,7 @@ def deprecator_from_collection_name(collection_name: str | None) -> _messages.Pl
19
19
 
20
20
  return _messages.PluginInfo(
21
21
  resolved_name=collection_name,
22
- type=_COLLECTION_ONLY_TYPE,
22
+ type=None,
23
23
  )
24
24
 
25
25
 
@@ -54,7 +54,7 @@ def _path_as_core_plugininfo(path: str) -> _messages.PluginInfo | None:
54
54
 
55
55
  if match := re.match(r'plugins/(?P<plugin_type>\w+)/(?P<plugin_name>\w+)', relpath):
56
56
  plugin_name = match.group("plugin_name")
57
- plugin_type = match.group("plugin_type")
57
+ plugin_type = _plugin_info.normalize_plugin_type(match.group("plugin_type"))
58
58
 
59
59
  if plugin_type not in _DEPRECATOR_PLUGIN_TYPES:
60
60
  # The plugin type isn't a known deprecator type, so we have to assume the caller is intermediate code.
@@ -65,13 +65,13 @@ def _path_as_core_plugininfo(path: str) -> _messages.PluginInfo | None:
65
65
  elif match := re.match(r'modules/(?P<module_name>\w+)', relpath):
66
66
  # AnsiballZ Python package for core modules
67
67
  plugin_name = match.group("module_name")
68
- plugin_type = "module"
68
+ plugin_type = _messages.PluginType.MODULE
69
69
  elif match := re.match(r'legacy/(?P<module_name>\w+)', relpath):
70
70
  # AnsiballZ Python package for non-core library/role modules
71
71
  namespace = 'ansible.legacy'
72
72
 
73
73
  plugin_name = match.group("module_name")
74
- plugin_type = "module"
74
+ plugin_type = _messages.PluginType.MODULE
75
75
  else:
76
76
  return ANSIBLE_CORE_DEPRECATOR # non-plugin core path, safe to use ansible-core for the same reason as the non-deprecator plugin type case above
77
77
 
@@ -85,7 +85,7 @@ def _path_as_collection_plugininfo(path: str) -> _messages.PluginInfo | None:
85
85
  if not (match := re.search(r'/ansible_collections/(?P<ns>\w+)/(?P<coll>\w+)/plugins/(?P<plugin_type>\w+)/(?P<plugin_name>\w+)', path)):
86
86
  return None
87
87
 
88
- plugin_type = match.group('plugin_type')
88
+ plugin_type = _plugin_info.normalize_plugin_type(match.group('plugin_type'))
89
89
 
90
90
  if plugin_type in _AMBIGUOUS_DEPRECATOR_PLUGIN_TYPES:
91
91
  # We're able to detect the namespace, collection and plugin type -- but we have no way to identify the plugin name currently.
@@ -93,9 +93,6 @@ def _path_as_collection_plugininfo(path: str) -> _messages.PluginInfo | None:
93
93
  # In the future we could improve the detection and/or make it easier for a caller to identify the plugin name.
94
94
  return deprecator_from_collection_name('.'.join((match.group('ns'), match.group('coll'))))
95
95
 
96
- if plugin_type == 'modules':
97
- plugin_type = 'module'
98
-
99
96
  if plugin_type not in _DEPRECATOR_PLUGIN_TYPES:
100
97
  # The plugin type isn't a known deprecator type, so we have to assume the caller is intermediate code.
101
98
  # We have no way of knowing if the intermediate code is deprecating its own feature, or acting on behalf of another plugin.
@@ -107,46 +104,43 @@ def _path_as_collection_plugininfo(path: str) -> _messages.PluginInfo | None:
107
104
  return _messages.PluginInfo(resolved_name=name, type=plugin_type)
108
105
 
109
106
 
110
- _COLLECTION_ONLY_TYPE: t.Final = 'collection'
111
- """Ersatz placeholder plugin type for use by a `PluginInfo` instance that references only a collection."""
112
-
113
107
  _ANSIBLE_MODULE_BASE_PATH: t.Final = pathlib.Path(sys.modules['ansible'].__file__).parent
114
108
  """Runtime-detected base path of the `ansible` Python package to distinguish between Ansible-owned and external code."""
115
109
 
116
110
  ANSIBLE_CORE_DEPRECATOR: t.Final = deprecator_from_collection_name('ansible.builtin')
117
111
  """Singleton `PluginInfo` instance for ansible-core callers where the plugin can/should not be identified in messages."""
118
112
 
119
- INDETERMINATE_DEPRECATOR: t.Final = _messages.PluginInfo(resolved_name='indeterminate', type='indeterminate')
113
+ INDETERMINATE_DEPRECATOR: t.Final = _messages.PluginInfo(resolved_name=None, type=None)
120
114
  """Singleton `PluginInfo` instance for indeterminate deprecator."""
121
115
 
122
116
  _DEPRECATOR_PLUGIN_TYPES: t.Final = frozenset(
123
117
  {
124
- 'action',
125
- 'become',
126
- 'cache',
127
- 'callback',
128
- 'cliconf',
129
- 'connection',
130
- # doc_fragments - no code execution
131
- # filter - basename inadequate to identify plugin
132
- 'httpapi',
133
- 'inventory',
134
- 'lookup',
135
- 'module', # only for collections
136
- 'netconf',
137
- 'shell',
138
- 'strategy',
139
- 'terminal',
140
- # test - basename inadequate to identify plugin
141
- 'vars',
118
+ _messages.PluginType.ACTION,
119
+ _messages.PluginType.BECOME,
120
+ _messages.PluginType.CACHE,
121
+ _messages.PluginType.CALLBACK,
122
+ _messages.PluginType.CLICONF,
123
+ _messages.PluginType.CONNECTION,
124
+ # DOC_FRAGMENTS - no code execution
125
+ # FILTER - basename inadequate to identify plugin
126
+ _messages.PluginType.HTTPAPI,
127
+ _messages.PluginType.INVENTORY,
128
+ _messages.PluginType.LOOKUP,
129
+ _messages.PluginType.MODULE, # only for collections
130
+ _messages.PluginType.NETCONF,
131
+ _messages.PluginType.SHELL,
132
+ _messages.PluginType.STRATEGY,
133
+ _messages.PluginType.TERMINAL,
134
+ # TEST - basename inadequate to identify plugin
135
+ _messages.PluginType.VARS,
142
136
  }
143
137
  )
144
138
  """Plugin types which are valid for identifying a deprecator for deprecation purposes."""
145
139
 
146
140
  _AMBIGUOUS_DEPRECATOR_PLUGIN_TYPES: t.Final = frozenset(
147
141
  {
148
- 'filter',
149
- 'test',
142
+ _messages.PluginType.FILTER,
143
+ _messages.PluginType.TEST,
150
144
  }
151
145
  )
152
146
  """Plugin types for which basename cannot be used to identify the plugin name."""
@@ -87,6 +87,7 @@ For controller-to-module, type behavior is profile dependent.
87
87
  _common_module_response_types: frozenset[type[AnsibleSerializable]] = frozenset(
88
88
  {
89
89
  _messages.PluginInfo,
90
+ _messages.PluginType,
90
91
  _messages.Event,
91
92
  _messages.EventChain,
92
93
  _messages.ErrorSummary,
@@ -8,6 +8,7 @@ A future release will remove the provisional status.
8
8
  from __future__ import annotations as _annotations
9
9
 
10
10
  import dataclasses as _dataclasses
11
+ import enum as _enum
11
12
  import sys as _sys
12
13
  import typing as _t
13
14
 
@@ -21,14 +22,37 @@ else:
21
22
  _dataclass_kwargs = dict(frozen=True)
22
23
 
23
24
 
25
+ class PluginType(_datatag.AnsibleSerializableEnum):
26
+ """Enum of Ansible plugin types."""
27
+
28
+ ACTION = _enum.auto()
29
+ BECOME = _enum.auto()
30
+ CACHE = _enum.auto()
31
+ CALLBACK = _enum.auto()
32
+ CLICONF = _enum.auto()
33
+ CONNECTION = _enum.auto()
34
+ DOC_FRAGMENTS = _enum.auto()
35
+ FILTER = _enum.auto()
36
+ HTTPAPI = _enum.auto()
37
+ INVENTORY = _enum.auto()
38
+ LOOKUP = _enum.auto()
39
+ MODULE = _enum.auto()
40
+ NETCONF = _enum.auto()
41
+ SHELL = _enum.auto()
42
+ STRATEGY = _enum.auto()
43
+ TERMINAL = _enum.auto()
44
+ TEST = _enum.auto()
45
+ VARS = _enum.auto()
46
+
47
+
24
48
  @_dataclasses.dataclass(**_dataclass_kwargs)
25
49
  class PluginInfo(_datatag.AnsibleSerializableDataclass):
26
50
  """Information about a loaded plugin."""
27
51
 
28
- resolved_name: str
52
+ resolved_name: _t.Optional[str]
29
53
  """The resolved canonical plugin name; always fully-qualified for collection plugins."""
30
54
 
31
- type: str
55
+ type: _t.Optional[PluginType]
32
56
  """The plugin type."""
33
57
 
34
58
 
@@ -21,5 +21,18 @@ def get_plugin_info(value: HasPluginInfo) -> _messages.PluginInfo:
21
21
  """Utility method that returns a `PluginInfo` from an object implementing the `HasPluginInfo` protocol."""
22
22
  return _messages.PluginInfo(
23
23
  resolved_name=value.ansible_name,
24
- type=value.plugin_type,
24
+ type=normalize_plugin_type(value.plugin_type),
25
25
  )
26
+
27
+
28
+ def normalize_plugin_type(value: str) -> _messages.PluginType | None:
29
+ """Normalize value and return it as a PluginType, or None if the value does match any known plugin type."""
30
+ value = value.lower()
31
+
32
+ if value == 'modules':
33
+ value = 'module'
34
+
35
+ try:
36
+ return _messages.PluginType(value)
37
+ except ValueError:
38
+ return None
@@ -17,6 +17,6 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = '2.19.0b5'
20
+ __version__ = '2.19.0b6'
21
21
  __author__ = 'Ansible, Inc.'
22
22
  __codename__ = "What Is and What Should Never Be"
@@ -480,9 +480,11 @@ class AnsibleModule(object):
480
480
  if basedir is not None and not os.path.exists(basedir):
481
481
  try:
482
482
  os.makedirs(basedir, mode=0o700)
483
- except (OSError, IOError) as e:
484
- self.warn("Unable to use %s as temporary directory, "
485
- "failing back to system: %s" % (basedir, to_native(e)))
483
+ except OSError as ex:
484
+ self.error_as_warning(
485
+ msg=f"Unable to use {basedir!r} as temporary directory, falling back to system default.",
486
+ exception=ex,
487
+ )
486
488
  basedir = None
487
489
  else:
488
490
  self.warn("Module remote_tmp %s did not exist and was "
@@ -494,11 +496,11 @@ class AnsibleModule(object):
494
496
  basefile = "ansible-moduletmp-%s-" % time.time()
495
497
  try:
496
498
  tmpdir = tempfile.mkdtemp(prefix=basefile, dir=basedir)
497
- except (OSError, IOError) as e:
498
- self.fail_json(
499
- msg="Failed to create remote module tmp path at dir %s "
500
- "with prefix %s: %s" % (basedir, basefile, to_native(e))
501
- )
499
+ except OSError as ex:
500
+ raise Exception(
501
+ f"Failed to create remote module tmp path at dir {basedir!r} "
502
+ f"with prefix {basefile!r}.",
503
+ ) from ex
502
504
  if not self._keep_remote_files:
503
505
  atexit.register(shutil.rmtree, tmpdir)
504
506
  self._tmpdir = tmpdir
@@ -658,11 +660,8 @@ class AnsibleModule(object):
658
660
  return context
659
661
  try:
660
662
  ret = selinux.lgetfilecon_raw(to_native(path, errors='surrogate_or_strict'))
661
- except OSError as e:
662
- if e.errno == errno.ENOENT:
663
- self.fail_json(path=path, msg='path %s does not exist' % path)
664
- else:
665
- self.fail_json(path=path, msg='failed to retrieve selinux context')
663
+ except OSError as ex:
664
+ self.fail_json(path=path, msg='Failed to retrieve selinux context.', exception=ex)
666
665
  if ret[0] == -1:
667
666
  return context
668
667
  # Limit split to 4 because the selevel, the last in the list,
@@ -802,9 +801,9 @@ class AnsibleModule(object):
802
801
  return True
803
802
  try:
804
803
  os.lchown(b_path, uid, -1)
805
- except (IOError, OSError) as e:
804
+ except OSError as ex:
806
805
  path = to_text(b_path)
807
- self.fail_json(path=path, msg='chown failed: %s' % (to_text(e)))
806
+ self.fail_json(path=path, msg='chown failed', exception=ex)
808
807
  changed = True
809
808
  return changed
810
809
 
@@ -1330,7 +1329,7 @@ class AnsibleModule(object):
1330
1329
  else:
1331
1330
  journal.send(MESSAGE=u"%s %s" % (module, journal_msg),
1332
1331
  **dict(journal_args))
1333
- except IOError:
1332
+ except OSError:
1334
1333
  # fall back to syslog since logging to journal failed
1335
1334
  self._log_to_syslog(journal_msg)
1336
1335
  else:
@@ -1660,8 +1659,8 @@ class AnsibleModule(object):
1660
1659
 
1661
1660
  try:
1662
1661
  self.preserved_copy(fn, backupdest)
1663
- except (shutil.Error, IOError) as e:
1664
- self.fail_json(msg='Could not make backup of %s to %s: %s' % (fn, backupdest, to_native(e)))
1662
+ except (shutil.Error, OSError) as ex:
1663
+ raise Exception(f'Could not make backup of {fn!r} to {backupdest!r}.') from ex
1665
1664
 
1666
1665
  return backupdest
1667
1666
 
@@ -1735,28 +1734,25 @@ class AnsibleModule(object):
1735
1734
  try:
1736
1735
  # Optimistically try a rename, solves some corner cases and can avoid useless work, throws exception if not atomic.
1737
1736
  os.rename(b_src, b_dest)
1738
- except (IOError, OSError) as e:
1739
- if e.errno not in [errno.EPERM, errno.EXDEV, errno.EACCES, errno.ETXTBSY, errno.EBUSY]:
1737
+ except OSError as ex:
1738
+ if ex.errno in (errno.EPERM, errno.EXDEV, errno.EACCES, errno.ETXTBSY, errno.EBUSY):
1740
1739
  # only try workarounds for errno 18 (cross device), 1 (not permitted), 13 (permission denied)
1741
1740
  # and 26 (text file busy) which happens on vagrant synced folders and other 'exotic' non posix file systems
1742
- self.fail_json(msg='Could not replace file: %s to %s: %s' % (src, dest, to_native(e)))
1743
- else:
1744
1741
  # Use bytes here. In the shippable CI, this fails with
1745
1742
  # a UnicodeError with surrogateescape'd strings for an unknown
1746
1743
  # reason (doesn't happen in a local Ubuntu16.04 VM)
1747
1744
  b_dest_dir = os.path.dirname(b_dest)
1748
1745
  b_suffix = os.path.basename(b_dest)
1749
- error_msg = None
1750
1746
  tmp_dest_name = None
1751
1747
  try:
1752
1748
  tmp_dest_fd, tmp_dest_name = tempfile.mkstemp(prefix=b'.ansible_tmp', dir=b_dest_dir, suffix=b_suffix)
1753
- except (OSError, IOError) as e:
1754
- error_msg = 'The destination directory (%s) is not writable by the current user. Error was: %s' % (os.path.dirname(dest), to_native(e))
1755
-
1749
+ except OSError as ex:
1756
1750
  if unsafe_writes:
1757
1751
  self._unsafe_writes(b_src, b_dest)
1758
1752
  else:
1759
- self.fail_json(msg=error_msg)
1753
+ raise Exception(
1754
+ f'The destination directory {os.path.dirname(dest)!r} is not writable by the current user.'
1755
+ ) from ex
1760
1756
 
1761
1757
  if tmp_dest_name:
1762
1758
  b_tmp_dest_name = to_bytes(tmp_dest_name, errors='surrogate_or_strict')
@@ -1785,24 +1781,27 @@ class AnsibleModule(object):
1785
1781
  if dest_stat and (tmp_stat.st_uid != dest_stat.st_uid or tmp_stat.st_gid != dest_stat.st_gid):
1786
1782
  os.chown(b_tmp_dest_name, dest_stat.st_uid, dest_stat.st_gid)
1787
1783
  os.utime(b_tmp_dest_name, times=(time.time(), time.time()))
1788
- except OSError as e:
1789
- if e.errno != errno.EPERM:
1784
+ except OSError as ex:
1785
+ if ex.errno != errno.EPERM:
1790
1786
  raise
1791
1787
  try:
1792
1788
  os.rename(b_tmp_dest_name, b_dest)
1793
- except (shutil.Error, OSError, IOError) as e:
1794
- if unsafe_writes and e.errno == errno.EBUSY:
1789
+ except (shutil.Error, OSError) as ex:
1790
+ if unsafe_writes and ex.errno == errno.EBUSY:
1795
1791
  self._unsafe_writes(b_tmp_dest_name, b_dest)
1796
1792
  else:
1797
- self.fail_json(msg='Unable to make %s into to %s, failed final rename from %s: %s' %
1798
- (src, dest, b_tmp_dest_name, to_native(e)))
1799
- except (shutil.Error, OSError, IOError) as e:
1793
+ raise Exception(
1794
+ f'Unable to make {src!r} into to {dest!r}, failed final rename from {to_text(b_tmp_dest_name)!r}.'
1795
+ ) from ex
1796
+ except (shutil.Error, OSError) as ex:
1800
1797
  if unsafe_writes:
1801
1798
  self._unsafe_writes(b_src, b_dest)
1802
1799
  else:
1803
- self.fail_json(msg='Failed to replace file: %s to %s: %s' % (src, dest, to_native(e)))
1800
+ raise Exception(f'Failed to replace {dest!r} with {src!r}.') from ex
1804
1801
  finally:
1805
1802
  self.cleanup(b_tmp_dest_name)
1803
+ else:
1804
+ raise Exception(f'Could not replace {dest!r} with {src!r}.') from ex
1806
1805
 
1807
1806
  if creating:
1808
1807
  # make sure the file has the correct permissions
@@ -1829,18 +1828,11 @@ class AnsibleModule(object):
1829
1828
  # sadly there are some situations where we cannot ensure atomicity, but only if
1830
1829
  # the user insists and we get the appropriate error we update the file unsafely
1831
1830
  try:
1832
- out_dest = in_src = None
1833
- try:
1834
- out_dest = open(dest, 'wb')
1835
- in_src = open(src, 'rb')
1836
- shutil.copyfileobj(in_src, out_dest)
1837
- finally: # assuring closed files in 2.4 compatible way
1838
- if out_dest:
1839
- out_dest.close()
1840
- if in_src:
1841
- in_src.close()
1842
- except (shutil.Error, OSError, IOError) as e:
1843
- self.fail_json(msg='Could not write data to file (%s) from (%s): %s' % (dest, src, to_native(e)))
1831
+ with open(dest, 'wb') as out_dest:
1832
+ with open(src, 'rb') as in_src:
1833
+ shutil.copyfileobj(in_src, out_dest)
1834
+ except (shutil.Error, OSError) as ex:
1835
+ raise Exception(f'Could not write data to file {dest!r} from {src!r}.') from ex
1844
1836
 
1845
1837
  def _clean_args(self, args):
1846
1838
 
@@ -2126,18 +2118,16 @@ class AnsibleModule(object):
2126
2118
  selector.close()
2127
2119
 
2128
2120
  rc = cmd.returncode
2129
- except (OSError, IOError) as e:
2130
- self.log("Error Executing CMD:%s Exception:%s" % (self._clean_args(args), to_native(e)))
2121
+ except OSError as ex:
2131
2122
  if handle_exceptions:
2132
- self.fail_json(rc=e.errno, stdout=b'', stderr=b'', msg=to_native(e), cmd=self._clean_args(args))
2123
+ self.fail_json(rc=ex.errno, stdout='', stderr='', msg="Error executing command.", cmd=self._clean_args(args), exception=ex)
2133
2124
  else:
2134
- raise e
2135
- except Exception as e:
2136
- self.log("Error Executing CMD:%s Exception:%s" % (self._clean_args(args), to_native(traceback.format_exc())))
2125
+ raise
2126
+ except Exception as ex:
2137
2127
  if handle_exceptions:
2138
- self.fail_json(rc=257, stdout=b'', stderr=b'', msg=to_native(e), cmd=self._clean_args(args))
2128
+ self.fail_json(rc=257, stdout='', stderr='', msg="Error executing command.", cmd=self._clean_args(args), exception=ex)
2139
2129
  else:
2140
- raise e
2130
+ raise
2141
2131
 
2142
2132
  if rc != 0 and check_rc:
2143
2133
  msg = heuristic_log_sanitize(stderr.rstrip(), self.no_log_values)
@@ -2208,7 +2198,7 @@ def __getattr__(importable_name):
2208
2198
  importable = repeat
2209
2199
  elif importable_name in {
2210
2200
  'PY2', 'PY3', 'b', 'binary_type', 'integer_types',
2211
- 'iteritems', 'string_types', 'test_type'
2201
+ 'iteritems', 'string_types', 'text_type',
2212
2202
  }:
2213
2203
  import importlib
2214
2204
  importable = getattr(
@@ -10,6 +10,7 @@ import sys
10
10
  import typing as t
11
11
 
12
12
  from ansible.module_utils.common.text.converters import to_bytes
13
+ from ansible.module_utils._internal._ansiballz import _respawn
13
14
 
14
15
  _ANSIBLE_PARENT_PATH = pathlib.Path(__file__).parents[3]
15
16
 
@@ -39,7 +40,7 @@ def respawn_module(interpreter_path) -> t.NoReturn:
39
40
  raise Exception('module has already been respawned')
40
41
 
41
42
  # FUTURE: we need a safe way to log that a respawn has occurred for forensic/debug purposes
42
- payload = _create_payload()
43
+ payload = _respawn.create_payload()
43
44
  stdin_read, stdin_write = os.pipe()
44
45
  os.write(stdin_write, to_bytes(payload))
45
46
  os.close(stdin_write)
@@ -59,10 +60,12 @@ def probe_interpreters_for_module(interpreter_paths, module_name):
59
60
  :arg module_name: fully-qualified Python module name to probe for (for example, ``selinux``)
60
61
  """
61
62
  PYTHONPATH = os.getenv('PYTHONPATH', '')
63
+
62
64
  env = os.environ.copy()
63
65
  env.update({
64
66
  'PYTHONPATH': f'{_ANSIBLE_PARENT_PATH}:{PYTHONPATH}'.rstrip(': ')
65
67
  })
68
+
66
69
  for interpreter_path in interpreter_paths:
67
70
  if not os.path.exists(interpreter_path):
68
71
  continue
@@ -81,43 +84,3 @@ def probe_interpreters_for_module(interpreter_paths, module_name):
81
84
  continue
82
85
 
83
86
  return None
84
-
85
-
86
- def _create_payload():
87
- # FIXME: move this into _ansiballz and skip the template
88
- from ansible.module_utils import basic
89
-
90
- module_fqn = sys.modules['__main__']._module_fqn
91
- modlib_path = sys.modules['__main__']._modlib_path
92
-
93
- respawn_code_template = """
94
- if __name__ == '__main__':
95
- import runpy
96
- import sys
97
-
98
- json_params = {json_params!r}
99
- profile = {profile!r}
100
- module_fqn = {module_fqn!r}
101
- modlib_path = {modlib_path!r}
102
-
103
- sys.path.insert(0, modlib_path)
104
-
105
- from ansible.module_utils._internal import _ansiballz
106
-
107
- _ansiballz.run_module(
108
- json_params=json_params,
109
- profile=profile,
110
- module_fqn=module_fqn,
111
- modlib_path=modlib_path,
112
- init_globals=dict(_respawned=True),
113
- )
114
- """
115
-
116
- respawn_code = respawn_code_template.format(
117
- json_params=basic._ANSIBLE_ARGS,
118
- profile=basic._ANSIBLE_PROFILE,
119
- module_fqn=module_fqn,
120
- modlib_path=modlib_path,
121
- )
122
-
123
- return respawn_code
@@ -33,7 +33,6 @@ import json
33
33
  import pickle
34
34
  import socket
35
35
  import struct
36
- import traceback
37
36
  import uuid
38
37
 
39
38
  from functools import partial
@@ -136,12 +135,11 @@ class Connection(object):
136
135
 
137
136
  try:
138
137
  out = self.send(data)
139
- except socket.error as e:
138
+ except OSError as ex:
140
139
  raise ConnectionError(
141
- 'unable to connect to socket %s. See Troubleshooting socket path issues '
142
- 'in the Network Debug and Troubleshooting Guide' % self.socket_path,
143
- err=to_text(e, errors='surrogate_then_replace'), exception=traceback.format_exc()
144
- )
140
+ f'Unable to connect to socket {self.socket_path!r}. See Troubleshooting socket path issues '
141
+ 'in the Network Debug and Troubleshooting Guide.'
142
+ ) from ex
145
143
 
146
144
  try:
147
145
  response = json.loads(out)
@@ -192,13 +190,12 @@ class Connection(object):
192
190
  send_data(sf, to_bytes(data))
193
191
  response = recv_data(sf)
194
192
 
195
- except socket.error as e:
193
+ except OSError as ex:
196
194
  sf.close()
197
195
  raise ConnectionError(
198
- 'unable to connect to socket %s. See the socket path issue category in '
199
- 'Network Debug and Troubleshooting Guide' % self.socket_path,
200
- err=to_text(e, errors='surrogate_then_replace'), exception=traceback.format_exc()
201
- )
196
+ f'Unable to connect to socket {self.socket_path!r}. See the socket path issue category in '
197
+ 'Network Debug and Troubleshooting Guide.',
198
+ ) from ex
202
199
 
203
200
  sf.close()
204
201
 
@@ -182,7 +182,7 @@ class LinuxHardware(Hardware):
182
182
  xen = True
183
183
  # Only interested in the first line
184
184
  break
185
- except IOError:
185
+ except OSError:
186
186
  pass
187
187
 
188
188
  if not os.access("/proc/cpuinfo", os.R_OK):
@@ -17,8 +17,6 @@ from __future__ import annotations
17
17
 
18
18
  import re
19
19
 
20
- from ansible.module_utils.common.text.converters import to_text
21
-
22
20
 
23
21
  def get_sysctl(module, prefixes):
24
22
 
@@ -31,8 +29,8 @@ def get_sysctl(module, prefixes):
31
29
 
32
30
  try:
33
31
  rc, out, err = module.run_command(cmd)
34
- except (IOError, OSError) as e:
35
- module.warn('Unable to read sysctl: %s' % to_text(e))
32
+ except OSError as ex:
33
+ module.error_as_warning('Unable to read sysctl.', exception=ex)
36
34
  rc = 1
37
35
 
38
36
  if rc == 0:
@@ -54,8 +52,8 @@ def get_sysctl(module, prefixes):
54
52
 
55
53
  try:
56
54
  (key, value) = re.split(r'\s?=\s?|: ', line, maxsplit=1)
57
- except Exception as e:
58
- module.warn('Unable to split sysctl line (%s): %s' % (to_text(line), to_text(e)))
55
+ except Exception as ex:
56
+ module.error_as_warning(f'Unable to split sysctl line {line!r}.', exception=ex)
59
57
 
60
58
  if key:
61
59
  sysctl[key] = value.strip()
@@ -38,8 +38,8 @@ class SystemCapabilitiesFactCollector(BaseFactCollector):
38
38
  # NOTE: -> get_caps_data()/parse_caps_data() for easier mocking -akl
39
39
  try:
40
40
  rc, out, err = module.run_command([capsh_path, "--print"], errors='surrogate_then_replace', handle_exceptions=False)
41
- except (IOError, OSError) as e:
42
- module.warn('Could not query system capabilities: %s' % str(e))
41
+ except OSError as ex:
42
+ module.error_as_warning('Could not query system capabilities.', exception=ex)
43
43
 
44
44
  if rc == 0:
45
45
  enforced_caps = []
@@ -50,7 +50,7 @@ class LocalFactCollector(BaseFactCollector):
50
50
  rc, out, err = module.run_command(fn)
51
51
  if rc != 0:
52
52
  failed = 'Failure executing fact script (%s), rc: %s, err: %s' % (fn, rc, err)
53
- except (IOError, OSError) as e:
53
+ except OSError as e:
54
54
  failed = 'Could not execute fact script (%s): %s' % (fn, to_text(e))
55
55
 
56
56
  if failed is not None:
@@ -129,7 +129,7 @@ class LinuxVirtual(Virtual):
129
129
  for line in get_file_lines('/proc/xen/capabilities'):
130
130
  if "control_d" in line:
131
131
  is_xen_host = True
132
- except IOError:
132
+ except OSError:
133
133
  pass
134
134
 
135
135
  if is_xen_host:
@@ -286,7 +286,7 @@ def is_systemd_managed(module):
286
286
  with open('/proc/1/comm', 'r') as init_proc:
287
287
  init = init_proc.readline().strip()
288
288
  return init == 'systemd'
289
- except IOError:
289
+ except OSError:
290
290
  # If comm doesn't exist, old kernel, no systemd
291
291
  return False
292
292
 
@@ -580,7 +580,7 @@ def get_ca_certs(cafile=None, capath=None):
580
580
  cadata[b_der] = None
581
581
  except Exception:
582
582
  continue
583
- except (OSError, IOError):
583
+ except OSError:
584
584
  pass
585
585
 
586
586
  # paths_checked isn't used any more, but is kept just for ease of debugging
@@ -694,7 +694,7 @@ def _configure_auth(url, url_username, url_password, use_gssapi, force_basic_aut
694
694
  try:
695
695
  rc = netrc.netrc(os.environ.get('NETRC'))
696
696
  login = rc.authenticators(parsed.hostname)
697
- except IOError:
697
+ except OSError:
698
698
  login = None
699
699
 
700
700
  if login:
@@ -1303,8 +1303,8 @@ def fetch_url(module, url, data=None, headers=None, method=None,
1303
1303
  except urllib.error.URLError as e:
1304
1304
  code = int(getattr(e, 'code', -1))
1305
1305
  info.update(dict(msg="Request failed: %s" % to_native(e), status=code))
1306
- except socket.error as e:
1307
- info.update(dict(msg="Connection failure: %s" % to_native(e), status=-1))
1306
+ except OSError as ex:
1307
+ info.update(dict(msg=f"Connection failure: {ex}", status=-1))
1308
1308
  except http.client.BadStatusLine as e:
1309
1309
  info.update(dict(msg="Connection failure: connection was closed before a valid response was received: %s" % to_native(e.line), status=-1))
1310
1310
  except Exception as ex: