ansible-core 2.19.0b1__py3-none-any.whl → 2.19.0b3__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 (92) hide show
  1. ansible/_internal/_ansiballz.py +1 -4
  2. ansible/_internal/_collection_proxy.py +47 -0
  3. ansible/_internal/_errors/_handler.py +4 -4
  4. ansible/_internal/_json/__init__.py +47 -4
  5. ansible/_internal/_json/_profiles/_legacy.py +2 -3
  6. ansible/_internal/_templating/_datatag.py +3 -4
  7. ansible/_internal/_templating/_engine.py +6 -1
  8. ansible/_internal/_templating/_jinja_bits.py +4 -4
  9. ansible/_internal/_templating/_jinja_plugins.py +7 -17
  10. ansible/cli/__init__.py +12 -5
  11. ansible/cli/arguments/option_helpers.py +4 -1
  12. ansible/cli/doc.py +14 -8
  13. ansible/config/base.yml +17 -20
  14. ansible/config/manager.py +2 -2
  15. ansible/constants.py +0 -62
  16. ansible/errors/__init__.py +6 -2
  17. ansible/executor/module_common.py +11 -7
  18. ansible/executor/process/worker.py +31 -26
  19. ansible/executor/task_executor.py +38 -31
  20. ansible/executor/task_queue_manager.py +62 -52
  21. ansible/executor/task_result.py +168 -72
  22. ansible/galaxy/api.py +1 -1
  23. ansible/galaxy/collection/__init__.py +3 -3
  24. ansible/inventory/manager.py +2 -1
  25. ansible/module_utils/_internal/_ansiballz.py +4 -30
  26. ansible/module_utils/_internal/_datatag/_tags.py +3 -25
  27. ansible/module_utils/_internal/_deprecator.py +134 -0
  28. ansible/module_utils/_internal/_plugin_info.py +25 -0
  29. ansible/module_utils/_internal/_validation.py +14 -0
  30. ansible/module_utils/ansible_release.py +1 -1
  31. ansible/module_utils/basic.py +68 -23
  32. ansible/module_utils/common/arg_spec.py +8 -3
  33. ansible/module_utils/common/messages.py +40 -23
  34. ansible/module_utils/common/process.py +0 -1
  35. ansible/module_utils/common/respawn.py +0 -7
  36. ansible/module_utils/common/warnings.py +13 -13
  37. ansible/module_utils/datatag.py +13 -13
  38. ansible/modules/async_status.py +1 -1
  39. ansible/modules/dnf5.py +1 -1
  40. ansible/modules/get_url.py +1 -1
  41. ansible/parsing/utils/jsonify.py +40 -0
  42. ansible/parsing/yaml/objects.py +16 -5
  43. ansible/playbook/included_file.py +25 -12
  44. ansible/playbook/task.py +0 -2
  45. ansible/plugins/__init__.py +18 -8
  46. ansible/plugins/action/__init__.py +6 -14
  47. ansible/plugins/action/gather_facts.py +2 -4
  48. ansible/plugins/callback/__init__.py +173 -86
  49. ansible/plugins/callback/default.py +79 -79
  50. ansible/plugins/callback/junit.py +20 -19
  51. ansible/plugins/callback/minimal.py +17 -17
  52. ansible/plugins/callback/oneline.py +23 -16
  53. ansible/plugins/callback/tree.py +13 -6
  54. ansible/plugins/connection/local.py +1 -1
  55. ansible/plugins/connection/paramiko_ssh.py +9 -2
  56. ansible/plugins/doc_fragments/action_core.py +1 -1
  57. ansible/plugins/filter/core.py +12 -2
  58. ansible/plugins/inventory/__init__.py +2 -2
  59. ansible/plugins/loader.py +194 -130
  60. ansible/plugins/lookup/url.py +2 -2
  61. ansible/plugins/strategy/__init__.py +76 -82
  62. ansible/plugins/strategy/free.py +4 -4
  63. ansible/plugins/strategy/linear.py +11 -9
  64. ansible/plugins/test/core.py +1 -1
  65. ansible/release.py +1 -1
  66. ansible/template/__init__.py +8 -6
  67. ansible/utils/collection_loader/_collection_meta.py +5 -3
  68. ansible/utils/display.py +141 -79
  69. ansible/utils/py3compat.py +1 -7
  70. ansible/utils/ssh_functions.py +4 -1
  71. ansible/utils/vars.py +23 -0
  72. ansible/vars/clean.py +1 -1
  73. ansible/vars/manager.py +18 -27
  74. ansible/vars/plugins.py +4 -4
  75. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/METADATA +1 -1
  76. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/RECORD +89 -85
  77. ansible_test/_internal/commands/sanity/pylint.py +1 -0
  78. ansible_test/_internal/docker_util.py +4 -3
  79. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +475 -0
  80. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_comment.py +137 -0
  81. ansible/module_utils/_internal/_dataclass_annotation_patch.py +0 -64
  82. ansible/module_utils/_internal/_plugin_exec_context.py +0 -49
  83. ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +0 -399
  84. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/Apache-License.txt +0 -0
  85. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/BSD-3-Clause.txt +0 -0
  86. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/COPYING +0 -0
  87. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/MIT-license.txt +0 -0
  88. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/PSF-license.txt +0 -0
  89. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/WHEEL +0 -0
  90. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/entry_points.txt +0 -0
  91. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/simplified_bsd.txt +0 -0
  92. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/top_level.txt +0 -0
@@ -42,7 +42,6 @@ def _ansiballz_main(
42
42
  module_fqn: str,
43
43
  params: str,
44
44
  profile: str,
45
- plugin_info_dict: dict[str, object],
46
45
  date_time: datetime.datetime,
47
46
  coverage_config: str | None,
48
47
  coverage_output: str | None,
@@ -142,7 +141,6 @@ def _ansiballz_main(
142
141
  run_module(
143
142
  json_params=json_params,
144
143
  profile=profile,
145
- plugin_info_dict=plugin_info_dict,
146
144
  module_fqn=module_fqn,
147
145
  modlib_path=modlib_path,
148
146
  coverage_config=coverage_config,
@@ -230,13 +228,12 @@ def _ansiballz_main(
230
228
  run_module(
231
229
  json_params=json_params,
232
230
  profile=profile,
233
- plugin_info_dict=plugin_info_dict,
234
231
  module_fqn=module_fqn,
235
232
  modlib_path=modlib_path,
236
233
  )
237
234
 
238
235
  else:
239
- print('WARNING: Unknown debug command. Doing nothing.')
236
+ print(f'FATAL: Unknown debug command {command!r}. Doing nothing.')
240
237
 
241
238
  #
242
239
  # See comments in the debug() method for information on debugging
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations as _annotations
2
+
3
+ import collections.abc as _c
4
+ import typing as _t
5
+
6
+ _T_co = _t.TypeVar('_T_co', covariant=True)
7
+
8
+
9
+ class SequenceProxy(_c.Sequence[_T_co]):
10
+ """A read-only sequence proxy."""
11
+
12
+ # DTFIX-RELEASE: needs unit test coverage
13
+
14
+ __slots__ = ('__value',)
15
+
16
+ def __init__(self, value: _c.Sequence[_T_co]) -> None:
17
+ self.__value = value
18
+
19
+ @_t.overload
20
+ def __getitem__(self, index: int) -> _T_co: ...
21
+
22
+ @_t.overload
23
+ def __getitem__(self, index: slice) -> _c.Sequence[_T_co]: ...
24
+
25
+ def __getitem__(self, index: int | slice) -> _T_co | _c.Sequence[_T_co]:
26
+ if isinstance(index, slice):
27
+ return self.__class__(self.__value[index])
28
+
29
+ return self.__value[index]
30
+
31
+ def __len__(self) -> int:
32
+ return len(self.__value)
33
+
34
+ def __contains__(self, item: object) -> bool:
35
+ return item in self.__value
36
+
37
+ def __iter__(self) -> _t.Iterator[_T_co]:
38
+ yield from self.__value
39
+
40
+ def __reversed__(self) -> _c.Iterator[_T_co]:
41
+ return reversed(self.__value)
42
+
43
+ def index(self, *args) -> int:
44
+ return self.__value.index(*args)
45
+
46
+ def count(self, value: object) -> int:
47
+ return self.__value.count(value)
@@ -16,8 +16,8 @@ class ErrorAction(enum.Enum):
16
16
  """Action to take when an error is encountered."""
17
17
 
18
18
  IGNORE = enum.auto()
19
- WARN = enum.auto()
20
- FAIL = enum.auto()
19
+ WARNING = enum.auto()
20
+ ERROR = enum.auto()
21
21
 
22
22
  @classmethod
23
23
  def from_config(cls, setting: str, variables: dict[str, t.Any] | None = None) -> t.Self:
@@ -75,9 +75,9 @@ class ErrorHandler:
75
75
  yield
76
76
  except args as ex:
77
77
  match self.action:
78
- case ErrorAction.WARN:
78
+ case ErrorAction.WARNING:
79
79
  display.error_as_warning(msg=None, exception=ex)
80
- case ErrorAction.FAIL:
80
+ case ErrorAction.ERROR:
81
81
  raise
82
82
  case _: # ErrorAction.IGNORE
83
83
  pass
@@ -4,6 +4,7 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
+ import enum
7
8
  import json
8
9
  import typing as t
9
10
 
@@ -19,7 +20,9 @@ from ansible.module_utils._internal._datatag import (
19
20
  from ansible.module_utils._internal._json._profiles import _tagless
20
21
  from ansible.parsing.vault import EncryptedString
21
22
  from ansible._internal._datatag._tags import Origin, TrustedAsTemplate
23
+ from ansible._internal._templating import _transform
22
24
  from ansible.module_utils import _internal
25
+ from ansible.module_utils._internal import _datatag
23
26
 
24
27
  _T = t.TypeVar('_T')
25
28
  _sentinel = object()
@@ -52,6 +55,19 @@ class StateTrackingMixIn(HasCurrent):
52
55
  return self._stack[1:] + [self._current]
53
56
 
54
57
 
58
+ class EncryptedStringBehavior(enum.Enum):
59
+ """How `AnsibleVariableVisitor` will handle instances of `EncryptedString`."""
60
+
61
+ PRESERVE = enum.auto()
62
+ """Preserves the unmodified `EncryptedString` instance."""
63
+ DECRYPT = enum.auto()
64
+ """Replaces the value with its decrypted plaintext."""
65
+ REDACT = enum.auto()
66
+ """Replaces the value with a placeholder string."""
67
+ FAIL = enum.auto()
68
+ """Raises an `AnsibleVariableTypeError` error."""
69
+
70
+
55
71
  class AnsibleVariableVisitor:
56
72
  """Utility visitor base class to recursively apply various behaviors and checks to variable object graphs."""
57
73
 
@@ -63,7 +79,9 @@ class AnsibleVariableVisitor:
63
79
  convert_mapping_to_dict: bool = False,
64
80
  convert_sequence_to_list: bool = False,
65
81
  convert_custom_scalars: bool = False,
66
- allow_encrypted_string: bool = False,
82
+ convert_to_native_values: bool = False,
83
+ apply_transforms: bool = False,
84
+ encrypted_string_behavior: EncryptedStringBehavior = EncryptedStringBehavior.DECRYPT,
67
85
  ):
68
86
  super().__init__() # supports StateTrackingMixIn
69
87
 
@@ -72,7 +90,16 @@ class AnsibleVariableVisitor:
72
90
  self.convert_mapping_to_dict = convert_mapping_to_dict
73
91
  self.convert_sequence_to_list = convert_sequence_to_list
74
92
  self.convert_custom_scalars = convert_custom_scalars
75
- self.allow_encrypted_string = allow_encrypted_string
93
+ self.convert_to_native_values = convert_to_native_values
94
+ self.apply_transforms = apply_transforms
95
+ self.encrypted_string_behavior = encrypted_string_behavior
96
+
97
+ if apply_transforms:
98
+ from ansible._internal._templating import _engine
99
+
100
+ self._template_engine = _engine.TemplateEngine()
101
+ else:
102
+ self._template_engine = None
76
103
 
77
104
  self._current: t.Any = None # supports StateTrackingMixIn
78
105
 
@@ -113,9 +140,19 @@ class AnsibleVariableVisitor:
113
140
 
114
141
  value_type = type(value)
115
142
 
143
+ if self.apply_transforms and value_type in _transform._type_transform_mapping:
144
+ value = self._template_engine.transform(value)
145
+ value_type = type(value)
146
+
147
+ # DTFIX-RELEASE: need to handle native copy for keys too
148
+ if self.convert_to_native_values and isinstance(value, _datatag.AnsibleTaggedObject):
149
+ value = value._native_copy()
150
+ value_type = type(value)
151
+
116
152
  result: _T
117
153
 
118
154
  # DTFIX-RELEASE: the visitor is ignoring dict/mapping keys except for debugging and schema-aware checking, it should be doing type checks on keys
155
+ # keep in mind the allowed types for keys is a more restrictive set than for values (str and taggged str only, not EncryptedString)
119
156
  # DTFIX-RELEASE: some type lists being consulted (the ones from datatag) are probably too permissive, and perhaps should not be dynamic
120
157
 
121
158
  if (result := self._early_visit(value, value_type)) is not _sentinel:
@@ -127,8 +164,14 @@ class AnsibleVariableVisitor:
127
164
  elif value_type in _ANSIBLE_ALLOWED_NON_SCALAR_COLLECTION_VAR_TYPES:
128
165
  with self: # supports StateTrackingMixIn
129
166
  result = AnsibleTagHelper.tag_copy(value, (self._visit(k, v) for k, v in enumerate(t.cast(t.Iterable, value))), value_type=value_type)
130
- elif self.allow_encrypted_string and isinstance(value, EncryptedString):
131
- return value # type: ignore[return-value] # DTFIX-RELEASE: this should probably only be allowed for values in dict, not keys (set, dict)
167
+ elif self.encrypted_string_behavior != EncryptedStringBehavior.FAIL and isinstance(value, EncryptedString):
168
+ match self.encrypted_string_behavior:
169
+ case EncryptedStringBehavior.REDACT:
170
+ result = "<redacted>" # type: ignore[assignment]
171
+ case EncryptedStringBehavior.PRESERVE:
172
+ result = value # type: ignore[assignment]
173
+ case EncryptedStringBehavior.DECRYPT:
174
+ result = str(value) # type: ignore[assignment]
132
175
  elif self.convert_mapping_to_dict and _internal.is_intermediate_mapping(value):
133
176
  with self: # supports StateTrackingMixIn
134
177
  result = {k: self._visit(k, v) for k, v in value.items()} # type: ignore[assignment]
@@ -8,13 +8,12 @@ from __future__ import annotations as _annotations
8
8
  import datetime as _datetime
9
9
  import typing as _t
10
10
 
11
+ from ansible._internal import _json
11
12
  from ansible._internal._datatag import _tags
12
13
  from ansible.module_utils._internal import _datatag
13
14
  from ansible.module_utils._internal._json import _profiles
14
15
  from ansible.parsing import vault as _vault
15
16
 
16
- from ... import _json
17
-
18
17
 
19
18
  class _Untrusted:
20
19
  """
@@ -48,7 +47,7 @@ class _LegacyVariableVisitor(_json.AnsibleVariableVisitor):
48
47
  convert_mapping_to_dict=convert_mapping_to_dict,
49
48
  convert_sequence_to_list=convert_sequence_to_list,
50
49
  convert_custom_scalars=convert_custom_scalars,
51
- allow_encrypted_string=True,
50
+ encrypted_string_behavior=_json.EncryptedStringBehavior.PRESERVE,
52
51
  )
53
52
 
54
53
  self.invert_trust = invert_trust
@@ -12,7 +12,6 @@ from ansible.utils.display import Display
12
12
  from ._access import NotifiableAccessContextBase
13
13
  from ._utils import TemplateContext
14
14
 
15
-
16
15
  display = Display()
17
16
 
18
17
 
@@ -57,10 +56,10 @@ class DeprecatedAccessAuditContext(NotifiableAccessContextBase):
57
56
  display._deprecated_with_plugin_info(
58
57
  msg=msg,
59
58
  help_text=item.deprecated.help_text,
60
- version=item.deprecated.removal_version,
61
- date=item.deprecated.removal_date,
59
+ version=item.deprecated.version,
60
+ date=item.deprecated.date,
62
61
  obj=item.template,
63
- plugin=item.deprecated.plugin,
62
+ deprecator=item.deprecated.deprecator,
64
63
  )
65
64
 
66
65
  return result
@@ -566,7 +566,12 @@ class TemplateEngine:
566
566
  )
567
567
 
568
568
  if _TemplateConfig.allow_broken_conditionals:
569
- _display.deprecated(msg=msg, obj=conditional, help_text=self._BROKEN_CONDITIONAL_ALLOWED_FRAGMENT, version='2.23')
569
+ _display.deprecated(
570
+ msg=msg,
571
+ obj=conditional,
572
+ help_text=self._BROKEN_CONDITIONAL_ALLOWED_FRAGMENT,
573
+ version='2.23',
574
+ )
570
575
 
571
576
  return bool_result
572
577
 
@@ -985,12 +985,12 @@ def _maybe_finalize_scalar(o: t.Any) -> t.Any:
985
985
 
986
986
  match _TemplateConfig.unknown_type_conversion_handler.action:
987
987
  # we don't want to show the object value, and it can't be Origin-tagged; send the current template value for best effort
988
- case ErrorAction.WARN:
988
+ case ErrorAction.WARNING:
989
989
  display.warning(
990
990
  msg=f'Type {native_type_name(o)!r} is unsupported in variable storage, converting to {native_type_name(target_type)!r}.',
991
991
  obj=TemplateContext.current(optional=True).template_value,
992
992
  )
993
- case ErrorAction.FAIL:
993
+ case ErrorAction.ERROR:
994
994
  raise AnsibleVariableTypeError.from_value(obj=TemplateContext.current(optional=True).template_value)
995
995
 
996
996
  return target_type(o)
@@ -1006,12 +1006,12 @@ def _finalize_fallback_collection(
1006
1006
  ) -> t.Collection[t.Any]:
1007
1007
  match _TemplateConfig.unknown_type_conversion_handler.action:
1008
1008
  # we don't want to show the object value, and it can't be Origin-tagged; send the current template value for best effort
1009
- case ErrorAction.WARN:
1009
+ case ErrorAction.WARNING:
1010
1010
  display.warning(
1011
1011
  msg=f'Type {native_type_name(o)!r} is unsupported in variable storage, converting to {native_type_name(target_type)!r}.',
1012
1012
  obj=TemplateContext.current(optional=True).template_value,
1013
1013
  )
1014
- case ErrorAction.FAIL:
1014
+ case ErrorAction.ERROR:
1015
1015
  raise AnsibleVariableTypeError.from_value(obj=TemplateContext.current(optional=True).template_value)
1016
1016
 
1017
1017
  return _finalize_collection(o, mode, finalizer, target_type)
@@ -8,12 +8,7 @@ import datetime
8
8
  import functools
9
9
  import typing as t
10
10
 
11
- from ansible.errors import (
12
- AnsibleTemplatePluginError,
13
- )
14
-
15
11
  from ansible.module_utils._internal._ambient_context import AmbientContextBase
16
- from ansible.module_utils._internal._plugin_exec_context import PluginExecContext
17
12
  from ansible.module_utils.common.collections import is_sequence
18
13
  from ansible.module_utils._internal._datatag import AnsibleTagHelper
19
14
  from ansible._internal._datatag._tags import TrustedAsTemplate
@@ -115,7 +110,7 @@ class JinjaPluginIntercept(c.MutableMapping):
115
110
  return first_marker
116
111
 
117
112
  try:
118
- with JinjaCallContext(accept_lazy_markers=instance.accept_lazy_markers), PluginExecContext(executing_plugin=instance):
113
+ with JinjaCallContext(accept_lazy_markers=instance.accept_lazy_markers):
119
114
  return instance.j2_function(*lazify_container_args(args), **lazify_container_kwargs(kwargs))
120
115
  except MarkerError as ex:
121
116
  return ex.source
@@ -216,10 +211,7 @@ def _invoke_lookup(*, plugin_name: str, lookup_terms: list, lookup_kwargs: dict[
216
211
  wantlist = lookup_kwargs.pop('wantlist', False)
217
212
  errors = lookup_kwargs.pop('errors', 'strict')
218
213
 
219
- with (
220
- JinjaCallContext(accept_lazy_markers=instance.accept_lazy_markers),
221
- PluginExecContext(executing_plugin=instance),
222
- ):
214
+ with JinjaCallContext(accept_lazy_markers=instance.accept_lazy_markers):
223
215
  try:
224
216
  if _TemplateConfig.allow_embedded_templates:
225
217
  # for backwards compat, only trust constant templates in lookup terms
@@ -263,15 +255,13 @@ def _invoke_lookup(*, plugin_name: str, lookup_terms: list, lookup_kwargs: dict[
263
255
  return ex.source
264
256
  except Exception as ex:
265
257
  # DTFIX-RELEASE: convert this to the new error/warn/ignore context manager
266
- if isinstance(ex, AnsibleTemplatePluginError):
267
- msg = f'Lookup failed but the error is being ignored: {ex}'
268
- else:
269
- msg = f'An unhandled exception occurred while running the lookup plugin {plugin_name!r}. Error was a {type(ex)}, original message: {ex}'
270
-
271
258
  if errors == 'warn':
272
- _display.warning(msg)
259
+ _display.error_as_warning(
260
+ msg=f'An error occurred while running the lookup plugin {plugin_name!r}.',
261
+ exception=ex,
262
+ )
273
263
  elif errors == 'ignore':
274
- _display.display(msg, log_only=True)
264
+ _display.display(f'An error of type {type(ex)} occurred while running the lookup plugin {plugin_name!r}: {ex}', log_only=True)
275
265
  else:
276
266
  raise AnsibleTemplatePluginRuntimeError('lookup', plugin_name) from ex
277
267
 
ansible/cli/__init__.py CHANGED
@@ -10,7 +10,6 @@ import os
10
10
  import signal
11
11
  import sys
12
12
 
13
-
14
13
  # We overload the ``ansible`` adhoc command to provide the functionality for
15
14
  # ``SSH_ASKPASS``. This code is here, and not in ``adhoc.py`` to bypass
16
15
  # unnecessary code. The program provided to ``SSH_ASKPASS`` can only be invoked
@@ -89,18 +88,25 @@ from ansible import _internal # do not remove or defer; ensures controller-spec
89
88
 
90
89
  _internal.setup()
91
90
 
91
+ from ansible.errors import AnsibleError, ExitCode
92
+
92
93
  try:
93
94
  from ansible import constants as C
94
95
  from ansible.utils.display import Display
95
96
  display = Display()
96
97
  except Exception as ex:
97
- print(f'ERROR: {ex}\n\n{"".join(traceback.format_exception(ex))}', file=sys.stderr)
98
+ if isinstance(ex, AnsibleError):
99
+ ex_msg = ' '.join((ex.message, ex._help_text)).strip()
100
+ else:
101
+ ex_msg = str(ex)
102
+
103
+ print(f'ERROR: {ex_msg}\n\n{"".join(traceback.format_exception(ex))}', file=sys.stderr)
98
104
  sys.exit(5)
99
105
 
100
106
 
101
107
  from ansible import context
108
+ from ansible.utils import display as _display
102
109
  from ansible.cli.arguments import option_helpers as opt_help
103
- from ansible.errors import AnsibleError, ExitCode
104
110
  from ansible.inventory.manager import InventoryManager
105
111
  from ansible.module_utils.six import string_types
106
112
  from ansible.module_utils.common.text.converters import to_bytes, to_text
@@ -116,6 +122,7 @@ from ansible.utils.collection_loader import AnsibleCollectionConfig
116
122
  from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path
117
123
  from ansible.utils.path import unfrackpath
118
124
  from ansible.vars.manager import VariableManager
125
+ from ansible.module_utils._internal import _deprecator
119
126
 
120
127
  try:
121
128
  import argcomplete
@@ -139,7 +146,7 @@ def _launch_ssh_agent() -> None:
139
146
  return
140
147
  case 'auto':
141
148
  try:
142
- ssh_agent_bin = get_bin_path('ssh-agent', required=True)
149
+ ssh_agent_bin = get_bin_path('ssh-agent')
143
150
  except ValueError as e:
144
151
  raise AnsibleError('SSH_AGENT set to auto, but cannot find ssh-agent binary') from e
145
152
  ssh_agent_dir = os.path.join(C.DEFAULT_LOCAL_TMP, 'ssh_agent')
@@ -251,7 +258,7 @@ class CLI(ABC):
251
258
  else:
252
259
  display.v(u"No config file found; using defaults")
253
260
 
254
- C.handle_config_noise(display)
261
+ _display._report_config_warnings(_deprecator.ANSIBLE_CORE_DEPRECATOR)
255
262
 
256
263
  @staticmethod
257
264
  def split_vault_id(vault_id):
@@ -56,7 +56,10 @@ class DeprecatedArgument:
56
56
 
57
57
  from ansible.utils.display import Display
58
58
 
59
- Display().deprecated(f'The {option!r} argument is deprecated.', version=self.version)
59
+ Display().deprecated( # pylint: disable=ansible-invalid-deprecated-version
60
+ msg=f'The {option!r} argument is deprecated.',
61
+ version=self.version,
62
+ )
60
63
 
61
64
 
62
65
  class ArgumentParser(argparse.ArgumentParser):
ansible/cli/doc.py CHANGED
@@ -1172,12 +1172,16 @@ class DocCLI(CLI, RoleMixin):
1172
1172
  return 'version %s' % (version_added, )
1173
1173
 
1174
1174
  @staticmethod
1175
- def warp_fill(text, limit, initial_indent='', subsequent_indent='', **kwargs):
1175
+ def warp_fill(text, limit, initial_indent='', subsequent_indent='', initial_extra=0, **kwargs):
1176
1176
  result = []
1177
1177
  for paragraph in text.split('\n\n'):
1178
- result.append(textwrap.fill(paragraph, limit, initial_indent=initial_indent, subsequent_indent=subsequent_indent,
1179
- break_on_hyphens=False, break_long_words=False, drop_whitespace=True, **kwargs))
1178
+ wrapped = textwrap.fill(paragraph, limit, initial_indent=initial_indent + ' ' * initial_extra, subsequent_indent=subsequent_indent,
1179
+ break_on_hyphens=False, break_long_words=False, drop_whitespace=True, **kwargs)
1180
+ if initial_extra and wrapped.startswith(' ' * initial_extra):
1181
+ wrapped = wrapped[initial_extra:]
1182
+ result.append(wrapped)
1180
1183
  initial_indent = subsequent_indent
1184
+ initial_extra = 0
1181
1185
  return '\n'.join(result)
1182
1186
 
1183
1187
  @staticmethod
@@ -1209,20 +1213,23 @@ class DocCLI(CLI, RoleMixin):
1209
1213
  text.append('')
1210
1214
 
1211
1215
  # TODO: push this to top of for and sort by size, create indent on largest key?
1212
- inline_indent = base_indent + ' ' * max((len(opt_indent) - len(o)) - len(base_indent), 2)
1213
- sub_indent = inline_indent + ' ' * (len(o) + 3)
1216
+ inline_indent = ' ' * max((len(opt_indent) - len(o)) - len(base_indent), 2)
1217
+ extra_indent = base_indent + ' ' * (len(o) + 3)
1218
+ sub_indent = inline_indent + extra_indent
1214
1219
  if is_sequence(opt['description']):
1215
1220
  for entry_idx, entry in enumerate(opt['description'], 1):
1216
1221
  if not isinstance(entry, string_types):
1217
1222
  raise AnsibleError("Expected string in description of %s at index %s, got %s" % (o, entry_idx, type(entry)))
1218
1223
  if entry_idx == 1:
1219
- text.append(key + DocCLI.warp_fill(DocCLI.tty_ify(entry), limit, initial_indent=inline_indent, subsequent_indent=sub_indent))
1224
+ text.append(key + DocCLI.warp_fill(DocCLI.tty_ify(entry), limit,
1225
+ initial_indent=inline_indent, subsequent_indent=sub_indent, initial_extra=len(extra_indent)))
1220
1226
  else:
1221
1227
  text.append(DocCLI.warp_fill(DocCLI.tty_ify(entry), limit, initial_indent=sub_indent, subsequent_indent=sub_indent))
1222
1228
  else:
1223
1229
  if not isinstance(opt['description'], string_types):
1224
1230
  raise AnsibleError("Expected string in description of %s, got %s" % (o, type(opt['description'])))
1225
- text.append(key + DocCLI.warp_fill(DocCLI.tty_ify(opt['description']), limit, initial_indent=inline_indent, subsequent_indent=sub_indent))
1231
+ text.append(key + DocCLI.warp_fill(DocCLI.tty_ify(opt['description']), limit,
1232
+ initial_indent=inline_indent, subsequent_indent=sub_indent, initial_extra=len(extra_indent)))
1226
1233
  del opt['description']
1227
1234
 
1228
1235
  suboptions = []
@@ -1328,7 +1335,6 @@ class DocCLI(CLI, RoleMixin):
1328
1335
  'This was unintentionally allowed when plugin attributes were added, '
1329
1336
  'but the feature does not map well to role argument specs.',
1330
1337
  version='2.20',
1331
- collection_name='ansible.builtin',
1332
1338
  )
1333
1339
  text.append("")
1334
1340
  text.append(_format("ATTRIBUTES:", 'bold'))
ansible/config/base.yml CHANGED
@@ -9,6 +9,18 @@ _ANSIBLE_CONNECTION_PATH:
9
9
  - For internal use only.
10
10
  type: path
11
11
  version_added: "2.18"
12
+ _CALLBACK_DISPATCH_ERROR_BEHAVIOR:
13
+ name: Callback dispatch error behavior
14
+ default: warning
15
+ description:
16
+ - Action to take when a callback dispatch results in an error.
17
+ type: choices
18
+ choices: &basic_error
19
+ error: issue a 'fatal' error and stop the play
20
+ warning: issue a warning but continue
21
+ ignore: just continue silently
22
+ env: [ { name: _ANSIBLE_CALLBACK_DISPATCH_ERROR_BEHAVIOR } ]
23
+ version_added: '2.19'
12
24
  ALLOW_BROKEN_CONDITIONALS:
13
25
  # This config option will be deprecated once it no longer has any effect (2.23).
14
26
  name: Allow broken conditionals
@@ -224,18 +236,6 @@ CACHE_PLUGIN_TIMEOUT:
224
236
  - {key: fact_caching_timeout, section: defaults}
225
237
  type: integer
226
238
  yaml: {key: facts.cache.timeout}
227
- _CALLBACK_DISPATCH_ERROR_BEHAVIOR:
228
- name: Callback dispatch error behavior
229
- default: warn
230
- description:
231
- - Action to take when a callback dispatch results in an error.
232
- type: choices
233
- choices: &choices_ignore_warn_fail
234
- - ignore
235
- - warn
236
- - fail
237
- env: [ { name: _ANSIBLE_CALLBACK_DISPATCH_ERROR_BEHAVIOR } ]
238
- version_added: '2.19'
239
239
  COLLECTIONS_SCAN_SYS_PATH:
240
240
  name: Scan PYTHONPATH for installed collections
241
241
  description: A boolean to enable or disable scanning the sys.path for installed collections.
@@ -268,10 +268,7 @@ COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH:
268
268
  - When a collection is loaded that does not support the running Ansible version (with the collection metadata key `requires_ansible`).
269
269
  env: [{name: ANSIBLE_COLLECTIONS_ON_ANSIBLE_VERSION_MISMATCH}]
270
270
  ini: [{key: collections_on_ansible_version_mismatch, section: defaults}]
271
- choices: &basic_error
272
- error: issue a 'fatal' error and stop the play
273
- warning: issue a warning but continue
274
- ignore: just continue silently
271
+ choices: *basic_error
275
272
  default: warning
276
273
  COLOR_CHANGED:
277
274
  name: Color for 'changed' task status
@@ -2058,13 +2055,13 @@ TASK_TIMEOUT:
2058
2055
  version_added: '2.10'
2059
2056
  _TEMPLAR_UNKNOWN_TYPE_CONVERSION:
2060
2057
  name: Templar unknown type conversion behavior
2061
- default: warn
2058
+ default: warning
2062
2059
  description:
2063
2060
  - Action to take when an unknown type is converted for variable storage during template finalization.
2064
2061
  - This setting has no effect on the inability to store unsupported variable types as the result of templating.
2065
2062
  - Experimental diagnostic feature, subject to change.
2066
2063
  type: choices
2067
- choices: *choices_ignore_warn_fail
2064
+ choices: *basic_error
2068
2065
  env: [{name: _ANSIBLE_TEMPLAR_UNKNOWN_TYPE_CONVERSION}]
2069
2066
  version_added: '2.19'
2070
2067
  _TEMPLAR_UNKNOWN_TYPE_ENCOUNTERED:
@@ -2074,7 +2071,7 @@ _TEMPLAR_UNKNOWN_TYPE_ENCOUNTERED:
2074
2071
  - Action to take when an unknown type is encountered inside a template pipeline.
2075
2072
  - Experimental diagnostic feature, subject to change.
2076
2073
  type: choices
2077
- choices: *choices_ignore_warn_fail
2074
+ choices: *basic_error
2078
2075
  env: [{name: _ANSIBLE_TEMPLAR_UNKNOWN_TYPE_ENCOUNTERED}]
2079
2076
  version_added: '2.19'
2080
2077
  _TEMPLAR_UNTRUSTED_TEMPLATE_BEHAVIOR:
@@ -2086,7 +2083,7 @@ _TEMPLAR_UNTRUSTED_TEMPLATE_BEHAVIOR:
2086
2083
  - This setting has no effect on expressions.
2087
2084
  - Experimental diagnostic feature, subject to change.
2088
2085
  type: choices
2089
- choices: *choices_ignore_warn_fail
2086
+ choices: *basic_error
2090
2087
  env: [{name: _ANSIBLE_TEMPLAR_UNTRUSTED_TEMPLATE_BEHAVIOR}]
2091
2088
  version_added: '2.19'
2092
2089
  WORKER_SHUTDOWN_POLL_COUNT:
ansible/config/manager.py CHANGED
@@ -480,9 +480,9 @@ class ConfigManager(object):
480
480
  else:
481
481
  ret = self._plugins.get(plugin_type, {}).get(name, {})
482
482
 
483
- if ignore_private:
483
+ if ignore_private: # ignore 'test' config entries, they should not change runtime behaviors
484
484
  for cdef in list(ret.keys()):
485
- if cdef.startswith('_'):
485
+ if cdef.startswith('_Z_'):
486
486
  del ret[cdef]
487
487
  return ret
488
488
 
ansible/constants.py CHANGED
@@ -10,9 +10,7 @@ from string import ascii_letters, digits
10
10
 
11
11
  from ansible.config.manager import ConfigManager
12
12
  from ansible.module_utils.common.text.converters import to_text
13
- from ansible.module_utils.common.collections import Sequence
14
13
  from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE
15
- from ansible.release import __version__
16
14
  from ansible.utils.fqcn import add_internal_fqcns
17
15
 
18
16
  # initialize config manager/config data to read/store global settings
@@ -20,68 +18,11 @@ from ansible.utils.fqcn import add_internal_fqcns
20
18
  config = ConfigManager()
21
19
 
22
20
 
23
- def _warning(msg):
24
- """ display is not guaranteed here, nor it being the full class, but try anyways, fallback to sys.stderr.write """
25
- try:
26
- from ansible.utils.display import Display
27
- Display().warning(msg)
28
- except Exception:
29
- import sys
30
- sys.stderr.write(' [WARNING] %s\n' % (msg))
31
-
32
-
33
- def _deprecated(msg, version):
34
- """ display is not guaranteed here, nor it being the full class, but try anyways, fallback to sys.stderr.write """
35
- try:
36
- from ansible.utils.display import Display
37
- Display().deprecated(msg, version=version)
38
- except Exception:
39
- import sys
40
- sys.stderr.write(' [DEPRECATED] %s, to be removed in %s\n' % (msg, version))
41
-
42
-
43
- def handle_config_noise(display=None):
44
-
45
- if display is not None:
46
- w = display.warning
47
- d = display.deprecated
48
- else:
49
- w = _warning
50
- d = _deprecated
51
-
52
- while config.WARNINGS:
53
- warn = config.WARNINGS.pop()
54
- w(warn)
55
-
56
- while config.DEPRECATED:
57
- # tuple with name and options
58
- dep = config.DEPRECATED.pop(0)
59
- msg = config.get_deprecated_msg_from_config(dep[1])
60
- # use tabs only for ansible-doc?
61
- msg = msg.replace("\t", "")
62
- d(f"{dep[0]} option. {msg}", version=dep[1]['version'])
63
-
64
-
65
21
  def set_constant(name, value, export=vars()):
66
22
  """ sets constants and returns resolved options dict """
67
23
  export[name] = value
68
24
 
69
25
 
70
- class _DeprecatedSequenceConstant(Sequence):
71
- def __init__(self, value, msg, version):
72
- self._value = value
73
- self._msg = msg
74
- self._version = version
75
-
76
- def __len__(self):
77
- _deprecated(self._msg, self._version)
78
- return len(self._value)
79
-
80
- def __getitem__(self, y):
81
- _deprecated(self._msg, self._version)
82
- return self._value[y]
83
-
84
-
85
26
  # CONSTANTS ### yes, actual ones
86
27
 
87
28
  # The following are hard-coded action names
@@ -245,6 +186,3 @@ MAGIC_VARIABLE_MAPPING = dict(
245
186
  # POPULATE SETTINGS FROM CONFIG ###
246
187
  for setting in config.get_configuration_definitions():
247
188
  set_constant(setting, config.get_config_value(setting, variables=vars()))
248
-
249
- # emit any warnings or deprecations
250
- handle_config_noise()