ansible-core 2.19.0b1__py3-none-any.whl → 2.19.0b2__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 (48) hide show
  1. ansible/_internal/_collection_proxy.py +47 -0
  2. ansible/_internal/_errors/_handler.py +4 -4
  3. ansible/_internal/_json/__init__.py +47 -4
  4. ansible/_internal/_json/_profiles/_legacy.py +2 -3
  5. ansible/_internal/_templating/_jinja_bits.py +4 -4
  6. ansible/_internal/_templating/_jinja_plugins.py +5 -11
  7. ansible/cli/__init__.py +9 -3
  8. ansible/cli/doc.py +14 -7
  9. ansible/config/base.yml +17 -20
  10. ansible/executor/process/worker.py +31 -26
  11. ansible/executor/task_executor.py +32 -23
  12. ansible/executor/task_queue_manager.py +62 -52
  13. ansible/executor/task_result.py +168 -72
  14. ansible/inventory/manager.py +2 -1
  15. ansible/module_utils/ansible_release.py +1 -1
  16. ansible/module_utils/basic.py +4 -6
  17. ansible/module_utils/common/warnings.py +1 -1
  18. ansible/parsing/utils/jsonify.py +40 -0
  19. ansible/parsing/yaml/objects.py +16 -5
  20. ansible/playbook/included_file.py +25 -12
  21. ansible/plugins/callback/__init__.py +173 -86
  22. ansible/plugins/callback/default.py +79 -79
  23. ansible/plugins/callback/junit.py +20 -19
  24. ansible/plugins/callback/minimal.py +17 -17
  25. ansible/plugins/callback/oneline.py +16 -15
  26. ansible/plugins/callback/tree.py +6 -5
  27. ansible/plugins/filter/core.py +8 -1
  28. ansible/plugins/strategy/__init__.py +70 -76
  29. ansible/plugins/strategy/free.py +4 -4
  30. ansible/plugins/strategy/linear.py +11 -9
  31. ansible/plugins/test/core.py +1 -1
  32. ansible/release.py +1 -1
  33. ansible/template/__init__.py +7 -5
  34. ansible/utils/display.py +10 -10
  35. ansible/utils/vars.py +23 -0
  36. ansible/vars/clean.py +1 -1
  37. ansible/vars/manager.py +2 -19
  38. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/METADATA +1 -1
  39. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/RECORD +48 -46
  40. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/Apache-License.txt +0 -0
  41. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/BSD-3-Clause.txt +0 -0
  42. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/COPYING +0 -0
  43. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/MIT-license.txt +0 -0
  44. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/PSF-license.txt +0 -0
  45. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/WHEEL +0 -0
  46. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/entry_points.txt +0 -0
  47. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/simplified_bsd.txt +0 -0
  48. {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b2.dist-info}/top_level.txt +0 -0
@@ -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
@@ -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,10 +8,6 @@ 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
12
  from ansible.module_utils._internal._plugin_exec_context import PluginExecContext
17
13
  from ansible.module_utils.common.collections import is_sequence
@@ -263,15 +259,13 @@ def _invoke_lookup(*, plugin_name: str, lookup_terms: list, lookup_kwargs: dict[
263
259
  return ex.source
264
260
  except Exception as ex:
265
261
  # 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
262
  if errors == 'warn':
272
- _display.warning(msg)
263
+ _display.error_as_warning(
264
+ msg=f'An error occurred while running the lookup plugin {plugin_name!r}.',
265
+ exception=ex,
266
+ )
273
267
  elif errors == 'ignore':
274
- _display.display(msg, log_only=True)
268
+ _display.display(f'An error of type {type(ex)} occurred while running the lookup plugin {plugin_name!r}: {ex}', log_only=True)
275
269
  else:
276
270
  raise AnsibleTemplatePluginRuntimeError('lookup', plugin_name) from ex
277
271
 
ansible/cli/__init__.py CHANGED
@@ -89,18 +89,24 @@ from ansible import _internal # do not remove or defer; ensures controller-spec
89
89
 
90
90
  _internal.setup()
91
91
 
92
+ from ansible.errors import AnsibleError, ExitCode
93
+
92
94
  try:
93
95
  from ansible import constants as C
94
96
  from ansible.utils.display import Display
95
97
  display = Display()
96
98
  except Exception as ex:
97
- print(f'ERROR: {ex}\n\n{"".join(traceback.format_exception(ex))}', file=sys.stderr)
99
+ if isinstance(ex, AnsibleError):
100
+ ex_msg = ' '.join((ex.message, ex._help_text)).strip()
101
+ else:
102
+ ex_msg = str(ex)
103
+
104
+ print(f'ERROR: {ex_msg}\n\n{"".join(traceback.format_exception(ex))}', file=sys.stderr)
98
105
  sys.exit(5)
99
106
 
100
107
 
101
108
  from ansible import context
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
@@ -139,7 +145,7 @@ def _launch_ssh_agent() -> None:
139
145
  return
140
146
  case 'auto':
141
147
  try:
142
- ssh_agent_bin = get_bin_path('ssh-agent', required=True)
148
+ ssh_agent_bin = get_bin_path('ssh-agent')
143
149
  except ValueError as e:
144
150
  raise AnsibleError('SSH_AGENT set to auto, but cannot find ssh-agent binary') from e
145
151
  ssh_agent_dir = os.path.join(C.DEFAULT_LOCAL_TMP, 'ssh_agent')
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 = []
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:
@@ -32,6 +32,7 @@ from ansible._internal import _task
32
32
  from ansible.errors import AnsibleConnectionFailure, AnsibleError
33
33
  from ansible.executor.task_executor import TaskExecutor
34
34
  from ansible.executor.task_queue_manager import FinalQueue, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO
35
+ from ansible.executor.task_result import _RawTaskResult
35
36
  from ansible.inventory.host import Host
36
37
  from ansible.module_utils.common.collections import is_sequence
37
38
  from ansible.module_utils.common.text.converters import to_text
@@ -226,7 +227,7 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
226
227
  init_plugin_loader(cli_collections_path)
227
228
 
228
229
  try:
229
- # execute the task and build a TaskResult from the result
230
+ # execute the task and build a _RawTaskResult from the result
230
231
  display.debug("running TaskExecutor() for %s/%s" % (self._host, self._task))
231
232
  executor_result = TaskExecutor(
232
233
  self._host,
@@ -256,48 +257,52 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
256
257
  # put the result on the result queue
257
258
  display.debug("sending task result for task %s" % self._task._uuid)
258
259
  try:
259
- self._final_q.send_task_result(
260
- self._host.name,
261
- self._task._uuid,
262
- executor_result,
260
+ self._final_q.send_task_result(_RawTaskResult(
261
+ host=self._host,
262
+ task=self._task,
263
+ return_data=executor_result,
263
264
  task_fields=self._task.dump_attrs(),
264
- )
265
+ ))
265
266
  except Exception as ex:
266
267
  try:
267
268
  raise AnsibleError("Task result omitted due to queue send failure.") from ex
268
269
  except Exception as ex_wrapper:
269
- self._final_q.send_task_result(
270
- self._host.name,
271
- self._task._uuid,
272
- ActionBase.result_dict_from_exception(ex_wrapper), # Overriding the task result, to represent the failure
273
- {}, # The failure pickling may have been caused by the task attrs, omit for safety
274
- )
270
+ self._final_q.send_task_result(_RawTaskResult(
271
+ host=self._host,
272
+ task=self._task,
273
+ return_data=ActionBase.result_dict_from_exception(ex_wrapper), # Overriding the task result, to represent the failure
274
+ task_fields={}, # The failure pickling may have been caused by the task attrs, omit for safety
275
+ ))
275
276
 
276
277
  display.debug("done sending task result for task %s" % self._task._uuid)
277
278
 
278
- except AnsibleConnectionFailure:
279
+ except AnsibleConnectionFailure as ex:
280
+ return_data = ActionBase.result_dict_from_exception(ex)
281
+ return_data.pop('failed')
282
+ return_data.update(unreachable=True)
283
+
279
284
  self._host.vars = dict()
280
285
  self._host.groups = []
281
- self._final_q.send_task_result(
282
- self._host.name,
283
- self._task._uuid,
284
- dict(unreachable=True),
286
+ self._final_q.send_task_result(_RawTaskResult(
287
+ host=self._host,
288
+ task=self._task,
289
+ return_data=return_data,
285
290
  task_fields=self._task.dump_attrs(),
286
- )
291
+ ))
287
292
 
288
- except Exception as e:
289
- if not isinstance(e, (IOError, EOFError, KeyboardInterrupt, SystemExit)) or isinstance(e, TemplateNotFound):
293
+ except Exception as ex:
294
+ if not isinstance(ex, (IOError, EOFError, KeyboardInterrupt, SystemExit)) or isinstance(ex, TemplateNotFound):
290
295
  try:
291
296
  self._host.vars = dict()
292
297
  self._host.groups = []
293
- self._final_q.send_task_result(
294
- self._host.name,
295
- self._task._uuid,
296
- dict(failed=True, exception=to_text(traceback.format_exc()), stdout=''),
298
+ self._final_q.send_task_result(_RawTaskResult(
299
+ host=self._host,
300
+ task=self._task,
301
+ return_data=ActionBase.result_dict_from_exception(ex),
297
302
  task_fields=self._task.dump_attrs(),
298
- )
303
+ ))
299
304
  except Exception:
300
- display.debug(u"WORKER EXCEPTION: %s" % to_text(e))
305
+ display.debug(u"WORKER EXCEPTION: %s" % to_text(ex))
301
306
  display.debug(u"WORKER TRACEBACK: %s" % to_text(traceback.format_exc()))
302
307
  finally:
303
308
  self._clean_up()
@@ -20,7 +20,7 @@ from ansible.errors import (
20
20
  AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleConnectionFailure, AnsibleActionFail, AnsibleActionSkip, AnsibleTaskError,
21
21
  AnsibleValueOmittedError,
22
22
  )
23
- from ansible.executor.task_result import TaskResult
23
+ from ansible.executor.task_result import _RawTaskResult
24
24
  from ansible._internal._datatag import _utils
25
25
  from ansible.module_utils._internal._plugin_exec_context import PluginExecContext
26
26
  from ansible.module_utils.common.messages import Detail, WarningSummary, DeprecationSummary
@@ -44,6 +44,9 @@ from ansible.vars.clean import namespace_facts, clean_facts
44
44
  from ansible.vars.manager import _deprecate_top_level_fact
45
45
  from ansible._internal._errors import _captured
46
46
 
47
+ if t.TYPE_CHECKING:
48
+ from ansible.executor.task_queue_manager import FinalQueue
49
+
47
50
  display = Display()
48
51
 
49
52
 
@@ -79,7 +82,7 @@ class TaskExecutor:
79
82
  class.
80
83
  """
81
84
 
82
- def __init__(self, host, task: Task, job_vars, play_context, loader, shared_loader_obj, final_q, variable_manager):
85
+ def __init__(self, host, task: Task, job_vars, play_context, loader, shared_loader_obj, final_q: FinalQueue, variable_manager):
83
86
  self._host = host
84
87
  self._task = task
85
88
  self._job_vars = job_vars
@@ -361,10 +364,10 @@ class TaskExecutor:
361
364
  if self._connection and not isinstance(self._connection, string_types):
362
365
  task_fields['connection'] = getattr(self._connection, 'ansible_name')
363
366
 
364
- tr = TaskResult(
365
- self._host.name,
366
- self._task._uuid,
367
- res,
367
+ tr = _RawTaskResult(
368
+ host=self._host,
369
+ task=self._task,
370
+ return_data=res,
368
371
  task_fields=task_fields,
369
372
  )
370
373
 
@@ -666,17 +669,23 @@ class TaskExecutor:
666
669
  if result.get('failed'):
667
670
  self._final_q.send_callback(
668
671
  'v2_runner_on_async_failed',
669
- TaskResult(self._host.name,
670
- self._task._uuid,
671
- result,
672
- task_fields=self._task.dump_attrs()))
672
+ _RawTaskResult(
673
+ host=self._host,
674
+ task=self._task,
675
+ return_data=result,
676
+ task_fields=self._task.dump_attrs(),
677
+ ),
678
+ )
673
679
  else:
674
680
  self._final_q.send_callback(
675
681
  'v2_runner_on_async_ok',
676
- TaskResult(self._host.name,
677
- self._task._uuid,
678
- result,
679
- task_fields=self._task.dump_attrs()))
682
+ _RawTaskResult(
683
+ host=self._host,
684
+ task=self._task,
685
+ return_data=result,
686
+ task_fields=self._task.dump_attrs(),
687
+ ),
688
+ )
680
689
 
681
690
  if 'ansible_facts' in result and self._task.action not in C._ACTION_DEBUG:
682
691
  if self._task.action in C._ACTION_WITH_CLEAN_FACTS:
@@ -756,12 +765,12 @@ class TaskExecutor:
756
765
  display.debug('Retrying task, attempt %d of %d' % (attempt, retries))
757
766
  self._final_q.send_callback(
758
767
  'v2_runner_retry',
759
- TaskResult(
760
- self._host.name,
761
- self._task._uuid,
762
- result,
768
+ _RawTaskResult(
769
+ host=self._host,
770
+ task=self._task,
771
+ return_data=result,
763
772
  task_fields=self._task.dump_attrs()
764
- )
773
+ ),
765
774
  )
766
775
  time.sleep(delay)
767
776
  self._handler = self._get_action_handler(templar=templar)
@@ -926,10 +935,10 @@ class TaskExecutor:
926
935
  time_left -= self._task.poll
927
936
  self._final_q.send_callback(
928
937
  'v2_runner_on_async_poll',
929
- TaskResult(
930
- self._host.name,
931
- async_task._uuid,
932
- async_result,
938
+ _RawTaskResult(
939
+ host=self._host,
940
+ task=async_task,
941
+ return_data=async_result,
933
942
  task_fields=async_task.dump_attrs(),
934
943
  ),
935
944
  )