ansible-core 2.19.0b4__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 (163) hide show
  1. ansible/_internal/__init__.py +1 -1
  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 +5 -5
  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/_yaml/_dumper.py +1 -1
  26. ansible/_internal/_yaml/_errors.py +7 -7
  27. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +1 -1
  28. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +1 -1
  29. ansible/cli/__init__.py +5 -82
  30. ansible/cli/arguments/option_helpers.py +2 -3
  31. ansible/cli/doc.py +84 -28
  32. ansible/cli/inventory.py +1 -1
  33. ansible/compat/importlib_resources.py +9 -12
  34. ansible/config/base.yml +22 -0
  35. ansible/errors/__init__.py +96 -49
  36. ansible/executor/module_common.py +8 -10
  37. ansible/executor/powershell/async_watchdog.ps1 +2 -2
  38. ansible/executor/powershell/async_wrapper.ps1 +3 -3
  39. ansible/executor/powershell/become_wrapper.ps1 +20 -2
  40. ansible/executor/powershell/bootstrap_wrapper.ps1 +28 -6
  41. ansible/executor/powershell/coverage_wrapper.ps1 +15 -6
  42. ansible/executor/powershell/exec_wrapper.ps1 +219 -6
  43. ansible/executor/powershell/module_manifest.py +52 -0
  44. ansible/executor/powershell/module_wrapper.ps1 +47 -21
  45. ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
  46. ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
  47. ansible/executor/process/worker.py +38 -113
  48. ansible/executor/task_executor.py +26 -61
  49. ansible/executor/task_result.py +2 -4
  50. ansible/galaxy/collection/__init__.py +1 -4
  51. ansible/inventory/manager.py +1 -1
  52. ansible/module_utils/_internal/__init__.py +0 -3
  53. ansible/module_utils/_internal/_ambient_context.py +3 -3
  54. ansible/module_utils/_internal/_ansiballz.py +4 -2
  55. ansible/module_utils/_internal/_datatag/__init__.py +20 -14
  56. ansible/module_utils/_internal/_datatag/_tags.py +2 -2
  57. ansible/module_utils/_internal/_deprecator.py +66 -48
  58. ansible/module_utils/_internal/_errors.py +88 -17
  59. ansible/module_utils/_internal/_event_utils.py +61 -0
  60. ansible/module_utils/_internal/_json/_profiles/__init__.py +21 -4
  61. ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +2 -0
  62. ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +2 -0
  63. ansible/module_utils/_internal/_json/_profiles/_tagless.py +3 -1
  64. ansible/module_utils/{common/messages.py → _internal/_messages.py} +28 -47
  65. ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
  66. ansible/module_utils/_internal/_plugin_info.py +1 -1
  67. ansible/module_utils/_internal/_stack.py +22 -0
  68. ansible/module_utils/_internal/_text_utils.py +6 -0
  69. ansible/module_utils/_internal/_traceback.py +11 -8
  70. ansible/module_utils/ansible_release.py +1 -1
  71. ansible/module_utils/basic.py +49 -15
  72. ansible/module_utils/common/arg_spec.py +2 -2
  73. ansible/module_utils/common/collections.py +6 -0
  74. ansible/module_utils/common/json.py +2 -2
  75. ansible/module_utils/common/text/converters.py +3 -3
  76. ansible/module_utils/common/validation.py +1 -1
  77. ansible/module_utils/common/warnings.py +80 -23
  78. ansible/module_utils/common/yaml.py +1 -1
  79. ansible/module_utils/datatag.py +5 -2
  80. ansible/module_utils/facts/system/distribution.py +16 -3
  81. ansible/module_utils/facts/virtual/linux.py +1 -1
  82. ansible/module_utils/service.py +2 -9
  83. ansible/modules/apt_repository.py +7 -29
  84. ansible/modules/async_status.py +13 -11
  85. ansible/modules/async_wrapper.py +5 -5
  86. ansible/modules/dnf5.py +14 -22
  87. ansible/modules/hostname.py +0 -1
  88. ansible/modules/service.py +3 -9
  89. ansible/parsing/ajson.py +3 -5
  90. ansible/parsing/dataloader.py +4 -4
  91. ansible/parsing/mod_args.py +1 -1
  92. ansible/parsing/plugin_docs.py +2 -2
  93. ansible/parsing/utils/yaml.py +3 -3
  94. ansible/parsing/vault/__init__.py +4 -4
  95. ansible/playbook/playbook_include.py +1 -1
  96. ansible/playbook/taggable.py +0 -3
  97. ansible/plugins/__init__.py +0 -25
  98. ansible/plugins/action/__init__.py +8 -31
  99. ansible/plugins/action/add_host.py +1 -1
  100. ansible/plugins/action/assemble.py +8 -16
  101. ansible/plugins/action/async_status.py +7 -2
  102. ansible/plugins/action/copy.py +8 -7
  103. ansible/plugins/action/gather_facts.py +8 -8
  104. ansible/plugins/action/package.py +5 -8
  105. ansible/plugins/action/script.py +8 -15
  106. ansible/plugins/action/service.py +3 -7
  107. ansible/plugins/action/template.py +3 -8
  108. ansible/plugins/action/unarchive.py +5 -15
  109. ansible/plugins/action/uri.py +9 -20
  110. ansible/plugins/callback/__init__.py +4 -6
  111. ansible/plugins/callback/junit.py +4 -2
  112. ansible/plugins/connection/local.py +2 -2
  113. ansible/plugins/connection/ssh.py +17 -9
  114. ansible/plugins/connection/winrm.py +5 -2
  115. ansible/plugins/doc_fragments/constructed.py +2 -2
  116. ansible/plugins/filter/core.py +13 -6
  117. ansible/plugins/filter/encryption.py +4 -4
  118. ansible/plugins/inventory/__init__.py +11 -10
  119. ansible/plugins/inventory/script.py +1 -1
  120. ansible/plugins/list.py +69 -16
  121. ansible/plugins/loader.py +7 -7
  122. ansible/plugins/lookup/csvfile.py +16 -71
  123. ansible/plugins/lookup/first_found.py +2 -1
  124. ansible/plugins/shell/__init__.py +56 -2
  125. ansible/plugins/shell/powershell.py +66 -9
  126. ansible/plugins/shell/sh.py +9 -5
  127. ansible/plugins/test/core.py +21 -15
  128. ansible/plugins/test/finished.yml +1 -1
  129. ansible/plugins/test/uri.py +2 -5
  130. ansible/release.py +1 -1
  131. ansible/template/__init__.py +30 -2
  132. ansible/utils/display.py +103 -128
  133. ansible/utils/hashing.py +0 -1
  134. ansible/utils/listify.py +6 -4
  135. ansible/utils/unsafe_proxy.py +1 -1
  136. ansible/vars/hostvars.py +1 -1
  137. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/METADATA +1 -1
  138. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/RECORD +162 -151
  139. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/WHEEL +1 -1
  140. ansible_test/_data/completion/docker.txt +3 -3
  141. ansible_test/_data/completion/remote.txt +1 -0
  142. ansible_test/_data/requirements/sanity.ansible-doc.txt +1 -1
  143. ansible_test/_data/requirements/sanity.changelog.txt +2 -2
  144. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  145. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  146. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  147. ansible_test/_internal/util.py +20 -0
  148. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +1 -0
  149. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
  150. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +1 -0
  151. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
  152. ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
  153. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +61 -7
  154. ansible_test/_util/target/setup/bootstrap.sh +31 -0
  155. ansible/_internal/_errors/_utils.py +0 -310
  156. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/entry_points.txt +0 -0
  157. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/COPYING +0 -0
  158. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  159. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  160. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  161. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  162. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  163. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/top_level.txt +0 -0
@@ -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.0b4'
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
 
@@ -201,7 +201,7 @@ class LinuxVirtual(Virtual):
201
201
  virtual_facts['virtualization_type'] = 'virtualbox'
202
202
  found_virt = True
203
203
 
204
- if bios_vendor in ('Amazon EC2', 'DigitalOcean', 'Hetzner'):
204
+ if bios_vendor in ('Amazon EC2', 'DigitalOcean', 'Hetzner', 'Linode'):
205
205
  guest_tech.add('kvm')
206
206
  if not found_virt:
207
207
  virtual_facts['virtualization_type'] = 'kvm'
@@ -36,7 +36,7 @@ import select
36
36
  import shlex
37
37
  import subprocess
38
38
 
39
- from ansible.module_utils.six import PY2, b
39
+ from ansible.module_utils.six import b
40
40
  from ansible.module_utils.common.text.converters import to_bytes, to_text
41
41
 
42
42
 
@@ -187,12 +187,8 @@ def daemonize(module, cmd):
187
187
  if pid == 0:
188
188
  os.close(pipe[0])
189
189
 
190
- # if command is string deal with py2 vs py3 conversions for shlex
191
190
  if not isinstance(cmd, list):
192
- if PY2:
193
- cmd = shlex.split(to_bytes(cmd, errors=errors))
194
- else:
195
- cmd = shlex.split(to_text(cmd, errors=errors))
191
+ cmd = shlex.split(to_text(cmd, errors=errors))
196
192
 
197
193
  # make sure we always use byte strings
198
194
  run_cmd = []
@@ -247,9 +243,6 @@ def daemonize(module, cmd):
247
243
  break
248
244
  return_data += to_bytes(data, errors=errors)
249
245
 
250
- # Note: no need to specify encoding on py3 as this module sends the
251
- # pickle to itself (thus same python interpreter so we aren't mixing
252
- # py2 and py3)
253
246
  return pickle.loads(to_bytes(return_data, errors=errors))
254
247
 
255
248
 
@@ -88,8 +88,8 @@ options:
88
88
  description:
89
89
  - Whether to automatically try to install the Python apt library or not, if it is not already installed.
90
90
  Without this library, the module does not work.
91
- - Runs C(apt-get install python-apt) for Python 2, and C(apt-get install python3-apt) for Python 3.
92
- - Only works with the system Python 2 or Python 3. If you are using a Python on the remote that is not
91
+ - Runs C(apt-get install python3-apt).
92
+ - Only works with the system Python. If you are using a Python on the remote that is not
93
93
  the system Python, set O(install_python_apt=false) and ensure that the Python apt library
94
94
  for your Python version is installed some other way.
95
95
  type: bool
@@ -98,8 +98,7 @@ author:
98
98
  - Alexander Saltanov (@sashka)
99
99
  version_added: "0.7"
100
100
  requirements:
101
- - python-apt (python 2)
102
- - python3-apt (python 3)
101
+ - python3-apt
103
102
  - apt-key or gpg
104
103
  """
105
104
 
@@ -232,14 +231,15 @@ class SourcesList(object):
232
231
  self.files_mapping = {} # internal DS for tracking symlinks
233
232
  # Repositories that we're adding -- used to implement mode param
234
233
  self.new_repos = set()
235
- self.default_file = self._apt_cfg_file('Dir::Etc::sourcelist')
234
+ self.default_file = apt_pkg.config.find_file('Dir::Etc::sourcelist')
236
235
 
237
236
  # read sources.list if it exists
238
237
  if os.path.isfile(self.default_file):
239
238
  self.load(self.default_file)
240
239
 
241
240
  # read sources.list.d
242
- for file in glob.iglob('%s/*.list' % self._apt_cfg_dir('Dir::Etc::sourceparts')):
241
+ self.sources_dir = apt_pkg.config.find_dir('Dir::Etc::sourceparts')
242
+ for file in glob.iglob(f'{self.sources_dir}/*.list'):
243
243
  if os.path.islink(file):
244
244
  self.files_mapping[file] = os.readlink(file)
245
245
  self.load(file)
@@ -255,7 +255,7 @@ class SourcesList(object):
255
255
  if '/' in filename:
256
256
  return filename
257
257
  else:
258
- return os.path.abspath(os.path.join(self._apt_cfg_dir('Dir::Etc::sourceparts'), filename))
258
+ return os.path.abspath(os.path.join(self.sources_dir, filename))
259
259
 
260
260
  def _suggest_filename(self, line):
261
261
  def _cleanup_filename(s):
@@ -313,28 +313,6 @@ class SourcesList(object):
313
313
 
314
314
  return valid, enabled, source, comment
315
315
 
316
- @staticmethod
317
- def _apt_cfg_file(filespec):
318
- """
319
- Wrapper for `apt_pkg` module for running with Python 2.5
320
- """
321
- try:
322
- result = apt_pkg.config.find_file(filespec)
323
- except AttributeError:
324
- result = apt_pkg.Config.FindFile(filespec)
325
- return result
326
-
327
- @staticmethod
328
- def _apt_cfg_dir(dirspec):
329
- """
330
- Wrapper for `apt_pkg` module for running with Python 2.5
331
- """
332
- try:
333
- result = apt_pkg.config.find_dir(dirspec)
334
- except AttributeError:
335
- result = apt_pkg.Config.FindDir(dirspec)
336
- return result
337
-
338
316
  def load(self, file):
339
317
  group = []
340
318
  f = open(file, 'r')
@@ -28,6 +28,8 @@ options:
28
28
  type: str
29
29
  choices: [ cleanup, status ]
30
30
  default: status
31
+ notes:
32
+ - The RV(started) and RV(finished) return values were updated to return V(True) or V(False) instead of V(1) or V(0) in ansible-core 2.19.
31
33
  extends_documentation_fragment:
32
34
  - action_common_attributes
33
35
  - action_common_attributes.flow
@@ -85,15 +87,15 @@ ansible_job_id:
85
87
  type: str
86
88
  sample: '360874038559.4169'
87
89
  finished:
88
- description: Whether the asynchronous job has finished (V(1)) or not (V(0))
90
+ description: Whether the asynchronous job has finished or not
89
91
  returned: always
90
- type: int
91
- sample: 1
92
+ type: bool
93
+ sample: true
92
94
  started:
93
- description: Whether the asynchronous job has started (V(1)) or not (V(0))
95
+ description: Whether the asynchronous job has started or not
94
96
  returned: always
95
- type: int
96
- sample: 1
97
+ type: bool
98
+ sample: true
97
99
  stdout:
98
100
  description: Any output returned by async_wrapper
99
101
  returned: always
@@ -134,7 +136,7 @@ def main():
134
136
  log_path = os.path.join(async_dir, jid)
135
137
 
136
138
  if not os.path.exists(log_path):
137
- module.fail_json(msg="could not find job", ansible_job_id=jid, started=1, finished=1)
139
+ module.fail_json(msg="could not find job", ansible_job_id=jid, started=True, finished=True)
138
140
 
139
141
  if mode == 'cleanup':
140
142
  os.unlink(log_path)
@@ -151,16 +153,16 @@ def main():
151
153
  except Exception:
152
154
  if not data:
153
155
  # file not written yet? That means it is running
154
- module.exit_json(results_file=log_path, ansible_job_id=jid, started=1, finished=0)
156
+ module.exit_json(results_file=log_path, ansible_job_id=jid, started=True, finished=False)
155
157
  else:
156
158
  module.fail_json(ansible_job_id=jid, results_file=log_path,
157
- msg="Could not parse job output: %s" % data, started=1, finished=1)
159
+ msg="Could not parse job output: %s" % data, started=True, finished=True)
158
160
 
159
161
  if 'started' not in data:
160
- data['finished'] = 1
162
+ data['finished'] = True
161
163
  data['ansible_job_id'] = jid
162
164
  elif 'finished' not in data:
163
- data['finished'] = 0
165
+ data['finished'] = False
164
166
 
165
167
  # just write the module output directly to stdout and exit; bypass other processing done by exit_json since it's already been done
166
168
  print(f"\n{json.dumps(data)}") # pylint: disable=ansible-bad-function