ansible-core 2.19.0b3__py3-none-any.whl → 2.19.0b5__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 (174) hide show
  1. ansible/_internal/__init__.py +2 -2
  2. ansible/_internal/_collection_proxy.py +1 -1
  3. ansible/_internal/_errors/_alarm_timeout.py +66 -0
  4. ansible/_internal/_errors/_captured.py +25 -30
  5. ansible/_internal/_errors/_error_factory.py +89 -0
  6. ansible/_internal/_errors/_error_utils.py +240 -0
  7. ansible/_internal/_errors/_task_timeout.py +28 -0
  8. ansible/_internal/_event_formatting.py +127 -0
  9. ansible/_internal/_json/__init__.py +6 -6
  10. ansible/_internal/_json/_profiles/_cache_persistence.py +2 -0
  11. ansible/_internal/_json/_profiles/_inventory_legacy.py +1 -1
  12. ansible/_internal/_json/_profiles/_legacy.py +3 -11
  13. ansible/_internal/_ssh/__init__.py +0 -0
  14. ansible/_internal/_ssh/_agent_launch.py +91 -0
  15. ansible/{utils → _internal/_ssh}/_ssh_agent.py +55 -93
  16. ansible/_internal/_templating/__init__.py +5 -3
  17. ansible/_internal/_templating/_datatag.py +2 -1
  18. ansible/_internal/_templating/_engine.py +3 -4
  19. ansible/_internal/_templating/_jinja_bits.py +21 -16
  20. ansible/_internal/_templating/_jinja_common.py +18 -27
  21. ansible/_internal/_templating/_jinja_plugins.py +31 -3
  22. ansible/_internal/_templating/_lazy_containers.py +5 -5
  23. ansible/_internal/_templating/_transform.py +20 -19
  24. ansible/_internal/_templating/_utils.py +1 -1
  25. ansible/_internal/_testing.py +26 -0
  26. ansible/_internal/_yaml/_dumper.py +1 -1
  27. ansible/_internal/_yaml/_errors.py +7 -7
  28. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +1 -1
  29. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +1 -1
  30. ansible/cli/__init__.py +5 -82
  31. ansible/cli/arguments/option_helpers.py +8 -5
  32. ansible/cli/doc.py +84 -28
  33. ansible/cli/inventory.py +1 -1
  34. ansible/compat/importlib_resources.py +9 -12
  35. ansible/config/base.yml +27 -23
  36. ansible/config/manager.py +142 -101
  37. ansible/constants.py +1 -1
  38. ansible/errors/__init__.py +96 -49
  39. ansible/executor/module_common.py +8 -10
  40. ansible/executor/powershell/async_watchdog.ps1 +2 -2
  41. ansible/executor/powershell/async_wrapper.ps1 +3 -3
  42. ansible/executor/powershell/become_wrapper.ps1 +20 -2
  43. ansible/executor/powershell/bootstrap_wrapper.ps1 +28 -6
  44. ansible/executor/powershell/coverage_wrapper.ps1 +15 -6
  45. ansible/executor/powershell/exec_wrapper.ps1 +219 -6
  46. ansible/executor/powershell/module_manifest.py +52 -0
  47. ansible/executor/powershell/module_wrapper.ps1 +47 -21
  48. ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
  49. ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
  50. ansible/executor/process/worker.py +38 -113
  51. ansible/executor/task_executor.py +26 -61
  52. ansible/executor/task_result.py +2 -4
  53. ansible/galaxy/collection/__init__.py +1 -4
  54. ansible/inventory/manager.py +1 -0
  55. ansible/module_utils/_internal/__init__.py +0 -3
  56. ansible/module_utils/_internal/_ambient_context.py +3 -3
  57. ansible/module_utils/_internal/_ansiballz.py +4 -2
  58. ansible/module_utils/_internal/_datatag/__init__.py +20 -14
  59. ansible/module_utils/_internal/_datatag/_tags.py +2 -2
  60. ansible/module_utils/_internal/_deprecator.py +66 -48
  61. ansible/module_utils/_internal/_errors.py +88 -17
  62. ansible/module_utils/_internal/_event_utils.py +61 -0
  63. ansible/module_utils/_internal/_json/_profiles/__init__.py +21 -4
  64. ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +2 -0
  65. ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +2 -0
  66. ansible/module_utils/_internal/_json/_profiles/_tagless.py +3 -1
  67. ansible/module_utils/{common/messages.py → _internal/_messages.py} +28 -47
  68. ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
  69. ansible/module_utils/_internal/_plugin_info.py +1 -1
  70. ansible/module_utils/_internal/_stack.py +22 -0
  71. ansible/module_utils/_internal/_text_utils.py +6 -0
  72. ansible/module_utils/_internal/_traceback.py +11 -8
  73. ansible/module_utils/ansible_release.py +1 -1
  74. ansible/module_utils/basic.py +49 -15
  75. ansible/module_utils/common/arg_spec.py +2 -2
  76. ansible/module_utils/common/collections.py +6 -0
  77. ansible/module_utils/common/json.py +2 -2
  78. ansible/module_utils/common/text/converters.py +3 -3
  79. ansible/module_utils/common/validation.py +1 -1
  80. ansible/module_utils/common/warnings.py +80 -23
  81. ansible/module_utils/common/yaml.py +1 -1
  82. ansible/module_utils/datatag.py +5 -2
  83. ansible/module_utils/facts/system/distribution.py +16 -3
  84. ansible/module_utils/facts/virtual/linux.py +2 -2
  85. ansible/module_utils/parsing/convert_bool.py +6 -0
  86. ansible/module_utils/service.py +2 -9
  87. ansible/modules/apt_repository.py +7 -29
  88. ansible/modules/assemble.py +4 -4
  89. ansible/modules/async_status.py +13 -11
  90. ansible/modules/async_wrapper.py +5 -5
  91. ansible/modules/cron.py +3 -5
  92. ansible/modules/dnf5.py +15 -22
  93. ansible/modules/git.py +1 -6
  94. ansible/modules/hostname.py +0 -1
  95. ansible/modules/pip.py +2 -4
  96. ansible/modules/service.py +3 -9
  97. ansible/modules/sysvinit.py +3 -3
  98. ansible/parsing/ajson.py +3 -5
  99. ansible/parsing/dataloader.py +4 -4
  100. ansible/parsing/mod_args.py +1 -1
  101. ansible/parsing/plugin_docs.py +2 -2
  102. ansible/parsing/utils/yaml.py +3 -3
  103. ansible/parsing/vault/__init__.py +4 -4
  104. ansible/playbook/playbook_include.py +1 -1
  105. ansible/playbook/taggable.py +0 -3
  106. ansible/plugins/__init__.py +0 -25
  107. ansible/plugins/action/__init__.py +9 -32
  108. ansible/plugins/action/add_host.py +1 -1
  109. ansible/plugins/action/assemble.py +8 -16
  110. ansible/plugins/action/async_status.py +7 -2
  111. ansible/plugins/action/copy.py +8 -7
  112. ansible/plugins/action/gather_facts.py +8 -8
  113. ansible/plugins/action/package.py +5 -8
  114. ansible/plugins/action/script.py +8 -15
  115. ansible/plugins/action/service.py +3 -7
  116. ansible/plugins/action/template.py +6 -8
  117. ansible/plugins/action/unarchive.py +5 -15
  118. ansible/plugins/action/uri.py +9 -20
  119. ansible/plugins/callback/__init__.py +4 -6
  120. ansible/plugins/callback/junit.py +4 -2
  121. ansible/plugins/connection/local.py +2 -2
  122. ansible/plugins/connection/ssh.py +17 -9
  123. ansible/plugins/connection/winrm.py +5 -2
  124. ansible/plugins/doc_fragments/constructed.py +2 -2
  125. ansible/plugins/filter/core.py +13 -6
  126. ansible/plugins/filter/encryption.py +4 -4
  127. ansible/plugins/inventory/__init__.py +11 -10
  128. ansible/plugins/inventory/script.py +1 -1
  129. ansible/plugins/list.py +69 -16
  130. ansible/plugins/loader.py +10 -9
  131. ansible/plugins/lookup/csvfile.py +16 -71
  132. ansible/plugins/lookup/first_found.py +2 -1
  133. ansible/plugins/shell/__init__.py +56 -2
  134. ansible/plugins/shell/powershell.py +66 -9
  135. ansible/plugins/shell/sh.py +9 -5
  136. ansible/plugins/test/core.py +21 -15
  137. ansible/plugins/test/finished.yml +1 -1
  138. ansible/plugins/test/uri.py +2 -5
  139. ansible/release.py +1 -1
  140. ansible/template/__init__.py +30 -2
  141. ansible/utils/collection_loader/__init__.py +2 -0
  142. ansible/utils/display.py +107 -128
  143. ansible/utils/hashing.py +0 -1
  144. ansible/utils/listify.py +6 -4
  145. ansible/utils/plugin_docs.py +2 -1
  146. ansible/utils/unsafe_proxy.py +1 -1
  147. ansible/vars/hostvars.py +1 -1
  148. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/METADATA +3 -2
  149. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/RECORD +173 -161
  150. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/WHEEL +1 -1
  151. ansible_test/_data/completion/docker.txt +3 -3
  152. ansible_test/_data/completion/remote.txt +1 -0
  153. ansible_test/_data/requirements/sanity.ansible-doc.txt +1 -1
  154. ansible_test/_data/requirements/sanity.changelog.txt +2 -2
  155. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  156. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  157. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  158. ansible_test/_internal/util.py +20 -0
  159. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +1 -0
  160. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
  161. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +1 -0
  162. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
  163. ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
  164. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +73 -8
  165. ansible_test/_util/target/setup/bootstrap.sh +31 -0
  166. ansible/_internal/_errors/_utils.py +0 -310
  167. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/entry_points.txt +0 -0
  168. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses}/COPYING +0 -0
  169. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/Apache-License.txt +0 -0
  170. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/BSD-3-Clause.txt +0 -0
  171. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/MIT-license.txt +0 -0
  172. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/PSF-license.txt +0 -0
  173. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/simplified_bsd.txt +0 -0
  174. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/top_level.txt +0 -0
@@ -7,13 +7,11 @@ A future release will remove the provisional status.
7
7
 
8
8
  from __future__ import annotations as _annotations
9
9
 
10
- import sys as _sys
11
10
  import dataclasses as _dataclasses
11
+ import sys as _sys
12
+ import typing as _t
12
13
 
13
- # deprecated: description='typing.Self exists in Python 3.11+' python_version='3.10'
14
- from ..compat import typing as _t
15
-
16
- from ansible.module_utils._internal import _datatag, _validation
14
+ from ansible.module_utils._internal import _datatag, _dataclass_validation
17
15
 
18
16
  if _sys.version_info >= (3, 10):
19
17
  # Using slots for reduced memory usage and improved performance.
@@ -29,50 +27,50 @@ class PluginInfo(_datatag.AnsibleSerializableDataclass):
29
27
 
30
28
  resolved_name: str
31
29
  """The resolved canonical plugin name; always fully-qualified for collection plugins."""
30
+
32
31
  type: str
33
32
  """The plugin type."""
34
33
 
35
- _COLLECTION_ONLY_TYPE: _t.ClassVar[str] = 'collection'
36
- """This is not a real plugin type. It's a placeholder for use by a `PluginInfo` instance which references a collection without a plugin."""
37
34
 
38
- @classmethod
39
- def _from_collection_name(cls, collection_name: str | None) -> _t.Self | None:
40
- """Returns an instance with the special `collection` type to refer to a non-plugin or ambiguous caller within a collection."""
41
- if not collection_name:
42
- return None
35
+ @_dataclasses.dataclass(**_dataclass_kwargs)
36
+ class EventChain(_datatag.AnsibleSerializableDataclass):
37
+ """A chain used to link one event to another."""
43
38
 
44
- _validation.validate_collection_name(collection_name)
39
+ _validation_auto_enabled = False
45
40
 
46
- return cls(
47
- resolved_name=collection_name,
48
- type=cls._COLLECTION_ONLY_TYPE,
49
- )
41
+ def __post_init__(self): ... # required for deferred dataclass validation
42
+
43
+ msg_reason: str
44
+ traceback_reason: str
45
+ event: Event
46
+ follow: bool = True
50
47
 
51
48
 
52
49
  @_dataclasses.dataclass(**_dataclass_kwargs)
53
- class Detail(_datatag.AnsibleSerializableDataclass):
54
- """Message detail with optional source context and help text."""
50
+ class Event(_datatag.AnsibleSerializableDataclass):
51
+ """Base class for an error/warning/deprecation event with optional chain (from an exception __cause__ chain) and an optional traceback."""
52
+
53
+ _validation_auto_enabled = False
54
+
55
+ def __post_init__(self): ... # required for deferred dataclass validation
55
56
 
56
57
  msg: str
57
58
  formatted_source_context: _t.Optional[str] = None
59
+ formatted_traceback: _t.Optional[str] = None
58
60
  help_text: _t.Optional[str] = None
61
+ chain: _t.Optional[EventChain] = None
62
+ events: _t.Optional[_t.Tuple[Event, ...]] = None
63
+
64
+
65
+ _dataclass_validation.inject_post_init_validation(EventChain, EventChain._validation_allow_subclasses)
66
+ _dataclass_validation.inject_post_init_validation(Event, Event._validation_allow_subclasses)
59
67
 
60
68
 
61
69
  @_dataclasses.dataclass(**_dataclass_kwargs)
62
70
  class SummaryBase(_datatag.AnsibleSerializableDataclass):
63
71
  """Base class for an error/warning/deprecation summary with details (possibly derived from an exception __cause__ chain) and an optional traceback."""
64
72
 
65
- details: _t.Tuple[Detail, ...]
66
- formatted_traceback: _t.Optional[str] = None
67
-
68
- def _format(self) -> str:
69
- """Returns a string representation of the details."""
70
- # DTFIX-RELEASE: eliminate this function and use a common message squashing utility such as get_chained_message on instances of this type
71
- return ': '.join(detail.msg for detail in self.details)
72
-
73
- def _post_validate(self) -> None:
74
- if not self.details:
75
- raise ValueError(f'{type(self).__name__}.details cannot be empty')
73
+ event: Event
76
74
 
77
75
 
78
76
  @_dataclasses.dataclass(**_dataclass_kwargs)
@@ -106,20 +104,3 @@ class DeprecationSummary(WarningSummary):
106
104
  Ignored if `deprecator` is not provided.
107
105
  Ignored if `date` is provided.
108
106
  """
109
-
110
- def _as_simple_dict(self) -> _t.Dict[str, _t.Any]:
111
- """Returns a dictionary representation of the deprecation object in the format exposed to playbooks."""
112
- from ansible.module_utils._internal._deprecator import INDETERMINATE_DEPRECATOR # circular import from messages
113
-
114
- if self.deprecator and self.deprecator != INDETERMINATE_DEPRECATOR:
115
- collection_name = '.'.join(self.deprecator.resolved_name.split('.')[:2])
116
- else:
117
- collection_name = None
118
-
119
- result = self._as_dict()
120
- result.update(
121
- msg=self._format(),
122
- collection_name=collection_name,
123
- )
124
-
125
- return result
@@ -1,7 +1,5 @@
1
1
  """Patches for builtin `dataclasses` module."""
2
2
 
3
- # deprecated: description='verify ClassVar support in dataclasses has been fixed in Python before removing this patching code', python_version='3.13'
4
-
5
3
  from __future__ import annotations
6
4
 
7
5
  import dataclasses
@@ -26,7 +24,7 @@ class DataclassesIsTypePatch(CallablePatch):
26
24
  @dataclasses.dataclass
27
25
  class CheckClassVar:
28
26
  # this is the broken case requiring patching: ClassVar dot-referenced from a module that is not `typing` is treated as an instance field
29
- # DTFIX-RELEASE: add link to CPython bug report to-be-filed (or update associated deprecation comments if we don't)
27
+ # DTFIX-FUTURE: file/link CPython bug report, deprecate this patch if/when it's fixed in CPython
30
28
  a_classvar: _ts.ClassVar[int] # type: ignore[name-defined]
31
29
  a_field: int
32
30
 
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import typing as t
4
4
 
5
- from ..common import messages as _messages
5
+ from . import _messages
6
6
 
7
7
 
8
8
  class HasPluginInfo(t.Protocol):
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations as _annotations
2
+
3
+ import inspect as _inspect
4
+ import typing as _t
5
+
6
+
7
+ def caller_frame() -> _inspect.FrameInfo | None:
8
+ """Return the caller stack frame, skipping any marked with the `_skip_stackwalk` local."""
9
+ _skip_stackwalk = True
10
+
11
+ return next(iter_stack(), None)
12
+
13
+
14
+ def iter_stack() -> _t.Generator[_inspect.FrameInfo]:
15
+ """Iterate over stack frames, skipping any marked with the `_skip_stackwalk` local."""
16
+ _skip_stackwalk = True
17
+
18
+ for frame_info in _inspect.stack():
19
+ if '_skip_stackwalk' in frame_info.frame.f_locals:
20
+ continue
21
+
22
+ yield frame_info
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations as _annotations
2
+
3
+
4
+ def concat_message(left: str, right: str) -> str:
5
+ """Normalize `left` by removing trailing punctuation and spaces before appending new punctuation and `right`."""
6
+ return f'{left.rstrip(". ")}: {right}'
@@ -6,9 +6,10 @@
6
6
  from __future__ import annotations
7
7
 
8
8
  import enum
9
- import inspect
10
9
  import traceback
11
10
 
11
+ from . import _stack
12
+
12
13
 
13
14
  class TracebackEvent(enum.Enum):
14
15
  """The events for which tracebacks can be enabled."""
@@ -16,6 +17,7 @@ class TracebackEvent(enum.Enum):
16
17
  ERROR = enum.auto()
17
18
  WARNING = enum.auto()
18
19
  DEPRECATED = enum.auto()
20
+ DEPRECATED_VALUE = enum.auto() # implies DEPRECATED
19
21
 
20
22
 
21
23
  def traceback_for() -> list[str]:
@@ -28,24 +30,25 @@ def is_traceback_enabled(event: TracebackEvent) -> bool:
28
30
  return _is_traceback_enabled(event)
29
31
 
30
32
 
31
- def maybe_capture_traceback(event: TracebackEvent) -> str | None:
33
+ def maybe_capture_traceback(msg: str, event: TracebackEvent) -> str | None:
32
34
  """
33
35
  Optionally capture a traceback for the current call stack, formatted as a string, if the specified traceback event is enabled.
34
- The current and previous frames are omitted to mask the expected call pattern from error/warning handlers.
36
+ Frames marked with the `_skip_stackwalk` local are omitted.
35
37
  """
38
+ _skip_stackwalk = True
39
+
36
40
  if not is_traceback_enabled(event):
37
41
  return None
38
42
 
39
43
  tb_lines = []
40
44
 
41
- if current_frame := inspect.currentframe():
45
+ if frame_info := _stack.caller_frame():
42
46
  # DTFIX-FUTURE: rewrite target-side tracebacks to point at controller-side paths?
43
- frames = inspect.getouterframes(current_frame)
44
- ignore_frame_count = 2 # ignore this function and its caller
45
47
  tb_lines.append('Traceback (most recent call last):\n')
46
- tb_lines.extend(traceback.format_stack(frames[ignore_frame_count].frame))
48
+ tb_lines.extend(traceback.format_stack(frame_info.frame))
49
+ tb_lines.append(f'Message: {msg}\n')
47
50
  else:
48
- tb_lines.append('Traceback unavailable.\n')
51
+ tb_lines.append('(frame not found)\n') # pragma: nocover
49
52
 
50
53
  return ''.join(tb_lines)
51
54
 
@@ -17,6 +17,6 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = '2.19.0b3'
20
+ __version__ = '2.19.0b5'
21
21
  __author__ = 'Ansible, Inc.'
22
22
  __codename__ = "What Is and What Should Never Be"
@@ -75,7 +75,7 @@ except ImportError:
75
75
  # Python2 & 3 way to get NoneType
76
76
  NoneType = type(None)
77
77
 
78
- from ._internal import _traceback, _errors, _debugging, _deprecator
78
+ from ._internal import _traceback, _errors, _debugging, _deprecator, _messages
79
79
 
80
80
  from .common.text.converters import (
81
81
  to_native,
@@ -161,10 +161,10 @@ from ansible.module_utils.common.validation import (
161
161
  safe_eval,
162
162
  )
163
163
  from ansible.module_utils.common._utils import get_all_subclasses as _get_all_subclasses
164
- from ansible.module_utils.common import messages as _messages
165
164
  from ansible.module_utils.parsing.convert_bool import BOOLEANS, BOOLEANS_FALSE, BOOLEANS_TRUE, boolean
166
165
  from ansible.module_utils.common.warnings import (
167
166
  deprecate,
167
+ error_as_warning,
168
168
  get_deprecations,
169
169
  get_warnings,
170
170
  warn,
@@ -505,9 +505,34 @@ class AnsibleModule(object):
505
505
 
506
506
  return self._tmpdir
507
507
 
508
- def warn(self, warning):
509
- warn(warning)
510
- self.log('[WARNING] %s' % warning)
508
+ def warn(
509
+ self,
510
+ warning: str,
511
+ *,
512
+ help_text: str | None = None,
513
+ ) -> None:
514
+ _skip_stackwalk = True
515
+
516
+ warn(
517
+ warning=warning,
518
+ help_text=help_text,
519
+ )
520
+
521
+ def error_as_warning(
522
+ self,
523
+ msg: str | None,
524
+ exception: BaseException,
525
+ *,
526
+ help_text: str | None = None,
527
+ ) -> None:
528
+ """Display an exception as a warning."""
529
+ _skip_stackwalk = True
530
+
531
+ error_as_warning(
532
+ msg=msg,
533
+ exception=exception,
534
+ help_text=help_text,
535
+ )
511
536
 
512
537
  def deprecate(
513
538
  self,
@@ -1399,7 +1424,7 @@ class AnsibleModule(object):
1399
1424
  # deprecate(
1400
1425
  # msg="The `AnsibleModule.jsonify' method is deprecated.",
1401
1426
  # version="2.27",
1402
- # # help_text="", # DTFIX-RELEASE: fill in this help text
1427
+ # # help_text="", # DTFIX-FUTURE: fill in this help text
1403
1428
  # )
1404
1429
 
1405
1430
  try:
@@ -1528,16 +1553,25 @@ class AnsibleModule(object):
1528
1553
  )
1529
1554
 
1530
1555
  if isinstance(exception, BaseException):
1531
- # Include a `_messages.ErrorDetail` in the result.
1532
- # The `msg` is included in the list of errors to ensure it is not lost when looking only at `exception` from the result.
1533
-
1534
- error_summary = _errors.create_error_summary(exception)
1535
- error_summary = _dataclasses.replace(error_summary, details=(_messages.Detail(msg=msg),) + error_summary.details)
1536
-
1537
- kwargs.update(exception=error_summary)
1556
+ # Include a `_messages.Event` in the result.
1557
+ # The `msg` is included in the chain to ensure it is not lost when looking only at `exception` from the result.
1558
+
1559
+ kwargs.update(
1560
+ exception=_messages.ErrorSummary(
1561
+ event=_messages.Event(
1562
+ msg=msg,
1563
+ formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.ERROR),
1564
+ chain=_messages.EventChain(
1565
+ msg_reason=_errors.MSG_REASON_DIRECT_CAUSE,
1566
+ traceback_reason="The above exception was the direct cause of the following error:",
1567
+ event=_errors.EventFactory.from_exception(exception, _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR)),
1568
+ ),
1569
+ ),
1570
+ ),
1571
+ )
1538
1572
  elif _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR):
1539
1573
  # Include only a formatted traceback string in the result.
1540
- # The controller will combine this with `msg` to create an `_messages.ErrorDetail`.
1574
+ # The controller will combine this with `msg` to create an `_messages.ErrorSummary`.
1541
1575
 
1542
1576
  formatted_traceback: str | None
1543
1577
 
@@ -1546,7 +1580,7 @@ class AnsibleModule(object):
1546
1580
  elif exception is _UNSET and (current_exception := t.cast(t.Optional[BaseException], sys.exc_info()[1])):
1547
1581
  formatted_traceback = _traceback.maybe_extract_traceback(current_exception, _traceback.TracebackEvent.ERROR)
1548
1582
  else:
1549
- formatted_traceback = _traceback.maybe_capture_traceback(_traceback.TracebackEvent.ERROR)
1583
+ formatted_traceback = _traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.ERROR)
1550
1584
 
1551
1585
  if formatted_traceback:
1552
1586
  kwargs.update(exception=formatted_traceback)
@@ -6,6 +6,7 @@ from __future__ import annotations
6
6
 
7
7
  from copy import deepcopy
8
8
 
9
+ from ansible.module_utils.datatag import deprecator_from_collection_name
9
10
  from ansible.module_utils.common.parameters import (
10
11
  _ADDITIONAL_CHECKS,
11
12
  _get_legal_inputs,
@@ -22,7 +23,6 @@ from ansible.module_utils.common.parameters import (
22
23
 
23
24
  from ansible.module_utils.common.text.converters import to_native
24
25
  from ansible.module_utils.common.warnings import deprecate, warn
25
- from ansible.module_utils.common import messages as _messages
26
26
 
27
27
  from ansible.module_utils.common.validation import (
28
28
  check_mutually_exclusive,
@@ -306,7 +306,7 @@ class ModuleArgumentSpecValidator(ArgumentSpecValidator):
306
306
  msg=d['msg'],
307
307
  version=d.get('version'),
308
308
  date=d.get('date'),
309
- deprecator=_messages.PluginInfo._from_collection_name(d.get('collection_name')),
309
+ deprecator=deprecator_from_collection_name(d.get('collection_name')),
310
310
  )
311
311
 
312
312
  for w in result._warnings:
@@ -6,6 +6,7 @@
6
6
  from __future__ import annotations
7
7
 
8
8
 
9
+ from ansible.module_utils.common import warnings as _warnings
9
10
  from ansible.module_utils.six import binary_type, text_type
10
11
  from ansible.module_utils.six.moves.collections_abc import Hashable, Mapping, MutableMapping, Sequence # pylint: disable=unused-import
11
12
 
@@ -102,6 +103,11 @@ def count(seq):
102
103
  code is run on Python 2.6.* where collections.Counter is not available. It should be
103
104
  deprecated and replaced when support for Python < 2.7 is dropped.
104
105
  """
106
+ _warnings.deprecate(
107
+ msg="The `ansible.module_utils.common.collections.count` function is deprecated.",
108
+ version="2.23",
109
+ help_text="Use `collections.Counter` from the Python standard library instead.",
110
+ )
105
111
  if not is_iterable(seq):
106
112
  raise Exception('Argument provided is not an iterable')
107
113
  counters = dict()
@@ -20,7 +20,7 @@ def __getattr__(name: str) -> object:
20
20
  # _warnings.deprecate(
21
21
  # msg="The `AnsibleJSONEncoder` type is deprecated.",
22
22
  # version="2.27",
23
- # help_text="Use a profile-based encoder instead.", # DTFIX-RELEASE: improve this help text
23
+ # help_text="Use a profile-based encoder instead.", # DTFIX-FUTURE: improve this help text
24
24
  # )
25
25
 
26
26
  return _get_legacy_encoder()
@@ -31,7 +31,7 @@ def __getattr__(name: str) -> object:
31
31
  # _warnings.deprecate(
32
32
  # msg="The `AnsibleJSONDecoder` type is deprecated.",
33
33
  # version="2.27",
34
- # help_text="Use a profile-based decoder instead.", # DTFIX-RELEASE: improve this help text
34
+ # help_text="Use a profile-based decoder instead.", # DTFIX-FUTURE: improve this help text
35
35
  # )
36
36
 
37
37
  return _tagless.Decoder
@@ -245,7 +245,7 @@ def jsonify(data, **kwargs):
245
245
  # deprecate(
246
246
  # msg="The `jsonify` function is deprecated.",
247
247
  # version="2.27",
248
- # # help_text="", # DTFIX-RELEASE: fill in this help text
248
+ # # help_text="", # DTFIX-FUTURE: fill in this help text
249
249
  # )
250
250
 
251
251
  return json.dumps(data, cls=_common_json._get_legacy_encoder(), _decode_bytes=True, **kwargs)
@@ -257,7 +257,7 @@ def container_to_bytes(d, encoding='utf-8', errors='surrogate_or_strict'):
257
257
  Specialized for json return because this only handles, lists, tuples,
258
258
  and dict container types (the containers that the json module returns)
259
259
  """
260
- # DTFIX-RELEASE: deprecate
260
+ # DTFIX-FUTURE: deprecate
261
261
 
262
262
  if isinstance(d, text_type):
263
263
  return to_bytes(d, encoding=encoding, errors=errors)
@@ -277,7 +277,7 @@ def container_to_text(d, encoding='utf-8', errors='surrogate_or_strict'):
277
277
  Specialized for json return because this only handles, lists, tuples,
278
278
  and dict container types (the containers that the json module returns)
279
279
  """
280
- # DTFIX-RELEASE: deprecate
280
+ # DTFIX-FUTURE: deprecate
281
281
 
282
282
  if isinstance(d, binary_type):
283
283
  # Warning, can traceback
@@ -402,7 +402,7 @@ def check_type_list(value):
402
402
  if isinstance(value, list):
403
403
  return value
404
404
 
405
- # DTFIX-RELEASE: deprecate legacy comma split functionality, eventually replace with `_check_type_list_strict`
405
+ # DTFIX-FUTURE: deprecate legacy comma split functionality, eventually replace with `_check_type_list_strict`
406
406
  if isinstance(value, string_types):
407
407
  return value.split(",")
408
408
  elif isinstance(value, int) or isinstance(value, float):
@@ -6,20 +6,77 @@ from __future__ import annotations as _annotations
6
6
 
7
7
  import typing as _t
8
8
 
9
- from ansible.module_utils._internal import _traceback, _deprecator
10
- from ansible.module_utils.common import messages as _messages
9
+ from ansible.module_utils._internal import _traceback, _deprecator, _event_utils, _messages, _errors
11
10
  from ansible.module_utils import _internal
12
11
 
13
12
 
14
- def warn(warning: str) -> None:
13
+ def warn(
14
+ warning: str,
15
+ *,
16
+ help_text: str | None = None,
17
+ obj: object | None = None,
18
+ ) -> None:
15
19
  """Record a warning to be returned with the module result."""
16
- # DTFIX-RELEASE: shim to controller display warning like `deprecate`
17
- _global_warnings[_messages.WarningSummary(
18
- details=(
19
- _messages.Detail(msg=warning),
20
+ _skip_stackwalk = True
21
+
22
+ if _internal.is_controller:
23
+ _display = _internal.import_controller_module('ansible.utils.display').Display()
24
+ _display.warning(
25
+ msg=warning,
26
+ help_text=help_text,
27
+ obj=obj,
28
+ )
29
+
30
+ return
31
+
32
+ warning = _messages.WarningSummary(
33
+ event=_messages.Event(
34
+ msg=warning,
35
+ help_text=help_text,
36
+ formatted_traceback=_traceback.maybe_capture_traceback(warning, _traceback.TracebackEvent.WARNING),
37
+ ),
38
+ )
39
+
40
+ _global_warnings[warning] = None
41
+
42
+
43
+ def error_as_warning(
44
+ msg: str | None,
45
+ exception: BaseException,
46
+ *,
47
+ help_text: str | None = None,
48
+ obj: object = None,
49
+ ) -> None:
50
+ """Display an exception as a warning."""
51
+ _skip_stackwalk = True
52
+
53
+ if _internal.is_controller:
54
+ _display = _internal.import_controller_module('ansible.utils.display').Display()
55
+ _display.error_as_warning(
56
+ msg=msg,
57
+ exception=exception,
58
+ help_text=help_text,
59
+ obj=obj,
60
+ )
61
+
62
+ return
63
+
64
+ event = _errors.EventFactory.from_exception(exception, _traceback.is_traceback_enabled(_traceback.TracebackEvent.WARNING))
65
+
66
+ warning = _messages.WarningSummary(
67
+ event=_messages.Event(
68
+ msg=msg,
69
+ help_text=help_text,
70
+ formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.WARNING),
71
+ chain=_messages.EventChain(
72
+ msg_reason=_errors.MSG_REASON_DIRECT_CAUSE,
73
+ traceback_reason=_errors.TRACEBACK_REASON_EXCEPTION_DIRECT_WARNING,
74
+ event=event,
75
+ ),
20
76
  ),
21
- formatted_traceback=_traceback.maybe_capture_traceback(_traceback.TracebackEvent.WARNING),
22
- )] = None
77
+ )
78
+
79
+ _global_warnings[warning] = None
23
80
 
24
81
 
25
82
  def deprecate(
@@ -57,30 +114,30 @@ def deprecate(
57
114
 
58
115
  return
59
116
 
60
- _global_deprecations[_messages.DeprecationSummary(
61
- details=(
62
- _messages.Detail(msg=msg, help_text=help_text),
117
+ warning = _messages.DeprecationSummary(
118
+ event=_messages.Event(
119
+ msg=msg,
120
+ help_text=help_text,
121
+ formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.DEPRECATED),
63
122
  ),
64
- formatted_traceback=_traceback.maybe_capture_traceback(_traceback.TracebackEvent.DEPRECATED),
65
123
  version=version,
66
124
  date=date,
67
125
  deprecator=deprecator,
68
- )] = None
126
+ )
127
+
128
+ _global_deprecations[warning] = None
69
129
 
70
130
 
71
131
  def get_warning_messages() -> tuple[str, ...]:
72
132
  """Return a tuple of warning messages accumulated over this run."""
73
- # DTFIX-RELEASE: add future deprecation comment
74
- return tuple(item._format() for item in _global_warnings)
75
-
76
-
77
- _DEPRECATION_MESSAGE_KEYS = frozenset({'msg', 'date', 'version', 'collection_name'})
133
+ # DTFIX7: add future deprecation comment
134
+ return tuple(_event_utils.format_event_brief_message(item.event) for item in _global_warnings)
78
135
 
79
136
 
80
137
  def get_deprecation_messages() -> tuple[dict[str, _t.Any], ...]:
81
138
  """Return a tuple of deprecation warning messages accumulated over this run."""
82
- # DTFIX-RELEASE: add future deprecation comment
83
- return tuple({key: value for key, value in item._as_simple_dict().items() if key in _DEPRECATION_MESSAGE_KEYS} for item in _global_deprecations)
139
+ # DTFIX7: add future deprecation comment
140
+ return tuple(_event_utils.deprecation_as_dict(item) for item in _global_deprecations)
84
141
 
85
142
 
86
143
  def get_warnings() -> list[_messages.WarningSummary]:
@@ -94,7 +151,7 @@ def get_deprecations() -> list[_messages.DeprecationSummary]:
94
151
 
95
152
 
96
153
  _global_warnings: dict[_messages.WarningSummary, object] = {}
97
- """Global, ordered, de-deplicated storage of acculumated warnings for the current module run."""
154
+ """Global, ordered, de-duplicated storage of accumulated warnings for the current module run."""
98
155
 
99
156
  _global_deprecations: dict[_messages.DeprecationSummary, object] = {}
100
- """Global, ordered, de-deplicated storage of acculumated deprecations for the current module run."""
157
+ """Global, ordered, de-duplicated storage of accumulated deprecations for the current module run."""
@@ -24,7 +24,7 @@ except ImportError:
24
24
  else:
25
25
  HAS_YAML = True
26
26
 
27
- # DTFIX-RELEASE: refactor this to share the implementation with the controller version
27
+ # DTFIX-FUTURE: refactor this to share the implementation with the controller version
28
28
  # use an abstract base class, with __init_subclass__ for representer registration, and instance methods for overridable representers
29
29
  # then tests can be consolidated intead of having two nearly identical copies
30
30
 
@@ -3,13 +3,15 @@ from __future__ import annotations as _annotations
3
3
 
4
4
  import typing as _t
5
5
 
6
- from ._internal import _datatag, _deprecator
6
+ from ._internal import _datatag, _deprecator, _traceback, _messages
7
7
  from ._internal._datatag import _tags
8
- from .common import messages as _messages
9
8
 
10
9
  _T = _t.TypeVar('_T')
11
10
 
12
11
 
12
+ deprecator_from_collection_name = _deprecator.deprecator_from_collection_name
13
+
14
+
13
15
  def deprecate_value(
14
16
  value: _T,
15
17
  msg: str,
@@ -36,6 +38,7 @@ def deprecate_value(
36
38
  date=date,
37
39
  version=version,
38
40
  deprecator=_deprecator.get_best_deprecator(deprecator=deprecator, collection_name=collection_name),
41
+ formatted_traceback=_traceback.maybe_capture_traceback(msg, _traceback.TracebackEvent.DEPRECATED_VALUE),
39
42
  )
40
43
 
41
44
  return deprecated.tag(value)
@@ -310,9 +310,22 @@ class DistributionFiles:
310
310
  suse_facts['distribution_release'] = release.group(1)
311
311
  suse_facts['distribution_version'] = collected_facts['distribution_version'] + '.' + release.group(1)
312
312
 
313
- # See https://www.suse.com/support/kb/doc/?id=000019341 for SLES for SAP
314
- if os.path.islink('/etc/products.d/baseproduct') and os.path.realpath('/etc/products.d/baseproduct').endswith('SLES_SAP.prod'):
315
- suse_facts['distribution'] = 'SLES_SAP'
313
+ # Check VARIANT_ID first for SLES4SAP or SL-Micro
314
+ variant_id_match = re.search(r'^VARIANT_ID="?([^"\n]*)"?', data, re.MULTILINE)
315
+ if variant_id_match:
316
+ variant_id = variant_id_match.group(1)
317
+ if variant_id in ('server-sap', 'sles-sap'):
318
+ suse_facts['distribution'] = 'SLES_SAP'
319
+ elif variant_id == 'transactional':
320
+ suse_facts['distribution'] = 'SL-Micro'
321
+ else:
322
+ # Fallback for older SLES 15 using baseproduct symlink
323
+ if os.path.islink('/etc/products.d/baseproduct'):
324
+ resolved = os.path.realpath('/etc/products.d/baseproduct')
325
+ if resolved.endswith('SLES_SAP.prod'):
326
+ suse_facts['distribution'] = 'SLES_SAP'
327
+ elif resolved.endswith('SL-Micro.prod'):
328
+ suse_facts['distribution'] = 'SL-Micro'
316
329
 
317
330
  return True, suse_facts
318
331