ansible-core 2.19.0b3__py3-none-any.whl → 2.19.0b5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ansible/_internal/__init__.py +2 -2
- ansible/_internal/_collection_proxy.py +1 -1
- ansible/_internal/_errors/_alarm_timeout.py +66 -0
- ansible/_internal/_errors/_captured.py +25 -30
- ansible/_internal/_errors/_error_factory.py +89 -0
- ansible/_internal/_errors/_error_utils.py +240 -0
- ansible/_internal/_errors/_task_timeout.py +28 -0
- ansible/_internal/_event_formatting.py +127 -0
- ansible/_internal/_json/__init__.py +6 -6
- ansible/_internal/_json/_profiles/_cache_persistence.py +2 -0
- ansible/_internal/_json/_profiles/_inventory_legacy.py +1 -1
- ansible/_internal/_json/_profiles/_legacy.py +3 -11
- ansible/_internal/_ssh/__init__.py +0 -0
- ansible/_internal/_ssh/_agent_launch.py +91 -0
- ansible/{utils → _internal/_ssh}/_ssh_agent.py +55 -93
- ansible/_internal/_templating/__init__.py +5 -3
- ansible/_internal/_templating/_datatag.py +2 -1
- ansible/_internal/_templating/_engine.py +3 -4
- ansible/_internal/_templating/_jinja_bits.py +21 -16
- ansible/_internal/_templating/_jinja_common.py +18 -27
- ansible/_internal/_templating/_jinja_plugins.py +31 -3
- ansible/_internal/_templating/_lazy_containers.py +5 -5
- ansible/_internal/_templating/_transform.py +20 -19
- ansible/_internal/_templating/_utils.py +1 -1
- ansible/_internal/_testing.py +26 -0
- ansible/_internal/_yaml/_dumper.py +1 -1
- ansible/_internal/_yaml/_errors.py +7 -7
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +1 -1
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +1 -1
- ansible/cli/__init__.py +5 -82
- ansible/cli/arguments/option_helpers.py +8 -5
- ansible/cli/doc.py +84 -28
- ansible/cli/inventory.py +1 -1
- ansible/compat/importlib_resources.py +9 -12
- ansible/config/base.yml +27 -23
- ansible/config/manager.py +142 -101
- ansible/constants.py +1 -1
- ansible/errors/__init__.py +96 -49
- ansible/executor/module_common.py +8 -10
- ansible/executor/powershell/async_watchdog.ps1 +2 -2
- ansible/executor/powershell/async_wrapper.ps1 +3 -3
- ansible/executor/powershell/become_wrapper.ps1 +20 -2
- ansible/executor/powershell/bootstrap_wrapper.ps1 +28 -6
- ansible/executor/powershell/coverage_wrapper.ps1 +15 -6
- ansible/executor/powershell/exec_wrapper.ps1 +219 -6
- ansible/executor/powershell/module_manifest.py +52 -0
- ansible/executor/powershell/module_wrapper.ps1 +47 -21
- ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
- ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
- ansible/executor/process/worker.py +38 -113
- ansible/executor/task_executor.py +26 -61
- ansible/executor/task_result.py +2 -4
- ansible/galaxy/collection/__init__.py +1 -4
- ansible/inventory/manager.py +1 -0
- ansible/module_utils/_internal/__init__.py +0 -3
- ansible/module_utils/_internal/_ambient_context.py +3 -3
- ansible/module_utils/_internal/_ansiballz.py +4 -2
- ansible/module_utils/_internal/_datatag/__init__.py +20 -14
- ansible/module_utils/_internal/_datatag/_tags.py +2 -2
- ansible/module_utils/_internal/_deprecator.py +66 -48
- ansible/module_utils/_internal/_errors.py +88 -17
- ansible/module_utils/_internal/_event_utils.py +61 -0
- ansible/module_utils/_internal/_json/_profiles/__init__.py +21 -4
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +2 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +2 -0
- ansible/module_utils/_internal/_json/_profiles/_tagless.py +3 -1
- ansible/module_utils/{common/messages.py → _internal/_messages.py} +28 -47
- ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
- ansible/module_utils/_internal/_plugin_info.py +1 -1
- ansible/module_utils/_internal/_stack.py +22 -0
- ansible/module_utils/_internal/_text_utils.py +6 -0
- ansible/module_utils/_internal/_traceback.py +11 -8
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +49 -15
- ansible/module_utils/common/arg_spec.py +2 -2
- ansible/module_utils/common/collections.py +6 -0
- ansible/module_utils/common/json.py +2 -2
- ansible/module_utils/common/text/converters.py +3 -3
- ansible/module_utils/common/validation.py +1 -1
- ansible/module_utils/common/warnings.py +80 -23
- ansible/module_utils/common/yaml.py +1 -1
- ansible/module_utils/datatag.py +5 -2
- ansible/module_utils/facts/system/distribution.py +16 -3
- ansible/module_utils/facts/virtual/linux.py +2 -2
- ansible/module_utils/parsing/convert_bool.py +6 -0
- ansible/module_utils/service.py +2 -9
- ansible/modules/apt_repository.py +7 -29
- ansible/modules/assemble.py +4 -4
- ansible/modules/async_status.py +13 -11
- ansible/modules/async_wrapper.py +5 -5
- ansible/modules/cron.py +3 -5
- ansible/modules/dnf5.py +15 -22
- ansible/modules/git.py +1 -6
- ansible/modules/hostname.py +0 -1
- ansible/modules/pip.py +2 -4
- ansible/modules/service.py +3 -9
- ansible/modules/sysvinit.py +3 -3
- ansible/parsing/ajson.py +3 -5
- ansible/parsing/dataloader.py +4 -4
- ansible/parsing/mod_args.py +1 -1
- ansible/parsing/plugin_docs.py +2 -2
- ansible/parsing/utils/yaml.py +3 -3
- ansible/parsing/vault/__init__.py +4 -4
- ansible/playbook/playbook_include.py +1 -1
- ansible/playbook/taggable.py +0 -3
- ansible/plugins/__init__.py +0 -25
- ansible/plugins/action/__init__.py +9 -32
- ansible/plugins/action/add_host.py +1 -1
- ansible/plugins/action/assemble.py +8 -16
- ansible/plugins/action/async_status.py +7 -2
- ansible/plugins/action/copy.py +8 -7
- ansible/plugins/action/gather_facts.py +8 -8
- ansible/plugins/action/package.py +5 -8
- ansible/plugins/action/script.py +8 -15
- ansible/plugins/action/service.py +3 -7
- ansible/plugins/action/template.py +6 -8
- ansible/plugins/action/unarchive.py +5 -15
- ansible/plugins/action/uri.py +9 -20
- ansible/plugins/callback/__init__.py +4 -6
- ansible/plugins/callback/junit.py +4 -2
- ansible/plugins/connection/local.py +2 -2
- ansible/plugins/connection/ssh.py +17 -9
- ansible/plugins/connection/winrm.py +5 -2
- ansible/plugins/doc_fragments/constructed.py +2 -2
- ansible/plugins/filter/core.py +13 -6
- ansible/plugins/filter/encryption.py +4 -4
- ansible/plugins/inventory/__init__.py +11 -10
- ansible/plugins/inventory/script.py +1 -1
- ansible/plugins/list.py +69 -16
- ansible/plugins/loader.py +10 -9
- ansible/plugins/lookup/csvfile.py +16 -71
- ansible/plugins/lookup/first_found.py +2 -1
- ansible/plugins/shell/__init__.py +56 -2
- ansible/plugins/shell/powershell.py +66 -9
- ansible/plugins/shell/sh.py +9 -5
- ansible/plugins/test/core.py +21 -15
- ansible/plugins/test/finished.yml +1 -1
- ansible/plugins/test/uri.py +2 -5
- ansible/release.py +1 -1
- ansible/template/__init__.py +30 -2
- ansible/utils/collection_loader/__init__.py +2 -0
- ansible/utils/display.py +107 -128
- ansible/utils/hashing.py +0 -1
- ansible/utils/listify.py +6 -4
- ansible/utils/plugin_docs.py +2 -1
- ansible/utils/unsafe_proxy.py +1 -1
- ansible/vars/hostvars.py +1 -1
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/METADATA +3 -2
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/RECORD +173 -161
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/WHEEL +1 -1
- ansible_test/_data/completion/docker.txt +3 -3
- ansible_test/_data/completion/remote.txt +1 -0
- ansible_test/_data/requirements/sanity.ansible-doc.txt +1 -1
- ansible_test/_data/requirements/sanity.changelog.txt +2 -2
- ansible_test/_data/requirements/sanity.pep8.txt +1 -1
- ansible_test/_data/requirements/sanity.pylint.txt +4 -4
- ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
- ansible_test/_internal/util.py +20 -0
- ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +73 -8
- ansible_test/_util/target/setup/bootstrap.sh +31 -0
- ansible/_internal/_errors/_utils.py +0 -310
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses}/COPYING +0 -0
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/Apache-License.txt +0 -0
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/MIT-license.txt +0 -0
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/PSF-license.txt +0 -0
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/top_level.txt +0 -0
@@ -97,6 +97,12 @@ $ps = [PowerShell]::Create()
|
|
97
97
|
if ($ForModule) {
|
98
98
|
$ps.Runspace.SessionStateProxy.SetVariable("ErrorActionPreference", "Stop")
|
99
99
|
}
|
100
|
+
else {
|
101
|
+
# For script files we want to ensure we load it as UTF-8. We don't set this
|
102
|
+
# for modules as they are loaded from memory whereas a script is loaded
|
103
|
+
# from disk as part of the script being run than by us.
|
104
|
+
Set-WinPSDefaultFileEncoding
|
105
|
+
}
|
100
106
|
|
101
107
|
foreach ($variable in $Variables) {
|
102
108
|
$null = $ps.AddCommand("Set-Variable").AddParameters($variable).AddStatement()
|
@@ -112,12 +118,31 @@ foreach ($env in $Environment.GetEnumerator()) {
|
|
112
118
|
$null = $ps.AddScript('Function Write-Host($msg) { Write-Output -InputObject $msg }').AddStatement()
|
113
119
|
|
114
120
|
$scriptInfo = Get-AnsibleScript -Name $Script
|
121
|
+
if ($scriptInfo.ShouldConstrain) {
|
122
|
+
# Fail if there are any module utils, in the future we may allow unsigned
|
123
|
+
# PowerShell utils in CLM but for now we don't.
|
124
|
+
if ($PowerShellModules -or $CSharpModules) {
|
125
|
+
throw "Cannot run untrusted PowerShell script '$Script' in ConstrainedLanguage mode with module util imports."
|
126
|
+
}
|
115
127
|
|
116
|
-
|
117
|
-
|
118
|
-
|
128
|
+
# If the module is marked as needing to be constrained then we set the
|
129
|
+
# language mode to ConstrainedLanguage so that when parsed inside the
|
130
|
+
# Runspace it will run in CLM. We need to run it from a filepath as in
|
131
|
+
# CLM we cannot call the methods needed to create the ScriptBlock and we
|
132
|
+
# need to be in CLM to downgrade the language mode.
|
133
|
+
$null = $ps.AddScript('$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"').AddStatement()
|
134
|
+
$scriptPath = New-TempAnsibleFile -FileName $Script -Content $scriptInfo.Script
|
135
|
+
$null = $ps.AddCommand($scriptPath, $false).AddStatement()
|
136
|
+
}
|
137
|
+
else {
|
138
|
+
if ($PowerShellModules) {
|
139
|
+
foreach ($utilName in $PowerShellModules) {
|
140
|
+
$utilInfo = Get-AnsibleScript -Name $utilName
|
141
|
+
if ($utilInfo.ShouldConstrain) {
|
142
|
+
throw "PowerShell module util '$utilName' is not trusted and cannot be loaded."
|
143
|
+
}
|
119
144
|
|
120
|
-
|
145
|
+
$null = $ps.AddScript(@'
|
121
146
|
param ($Name, $Script)
|
122
147
|
|
123
148
|
$moduleName = [System.IO.Path]::GetFileNameWithoutExtension($Name)
|
@@ -130,32 +155,33 @@ $sbk = [System.Management.Automation.Language.Parser]::ParseInput(
|
|
130
155
|
New-Module -Name $moduleName -ScriptBlock $sbk |
|
131
156
|
Import-Module -WarningAction SilentlyContinue -Scope Global
|
132
157
|
'@, $true)
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
158
|
+
$null = $ps.AddParameters(
|
159
|
+
@{
|
160
|
+
Name = $utilName
|
161
|
+
Script = $utilInfo.Script
|
162
|
+
}
|
163
|
+
).AddStatement()
|
164
|
+
}
|
139
165
|
}
|
140
|
-
}
|
141
166
|
|
142
|
-
if ($CSharpModules) {
|
143
|
-
|
144
|
-
|
145
|
-
}
|
167
|
+
if ($CSharpModules) {
|
168
|
+
# C# utils are process wide so just load them here.
|
169
|
+
Import-CSharpUtil -Name $CSharpModules
|
170
|
+
}
|
146
171
|
|
147
|
-
# We invoke it through a command with useLocalScope $false to
|
148
|
-
# ensure the code runs with it's own $script: scope. It also
|
149
|
-
# cleans up the StackTrace on errors by not showing the stub
|
150
|
-
# execution line and starts immediately at the module "cmd".
|
151
|
-
$null = $ps.AddScript(@'
|
172
|
+
# We invoke it through a command with useLocalScope $false to
|
173
|
+
# ensure the code runs with it's own $script: scope. It also
|
174
|
+
# cleans up the StackTrace on errors by not showing the stub
|
175
|
+
# execution line and starts immediately at the module "cmd".
|
176
|
+
$null = $ps.AddScript(@'
|
152
177
|
${function:<AnsibleModule>} = [System.Management.Automation.Language.Parser]::ParseInput(
|
153
178
|
$args[0],
|
154
179
|
$args[1],
|
155
180
|
[ref]$null,
|
156
181
|
[ref]$null).GetScriptBlock()
|
157
182
|
'@).AddArgument($scriptInfo.Script).AddArgument($Script).AddStatement()
|
158
|
-
$null = $ps.AddCommand('<AnsibleModule>', $false).AddStatement()
|
183
|
+
$null = $ps.AddCommand('<AnsibleModule>', $false).AddStatement()
|
184
|
+
}
|
159
185
|
|
160
186
|
if ($Breakpoints) {
|
161
187
|
$ps.Runspace.Debugger.SetBreakpoints($Breakpoints)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# (c) 2025 Ansible Project
|
2
|
+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
|
4
|
+
[CmdletBinding()]
|
5
|
+
param (
|
6
|
+
[Parameter(Mandatory)]
|
7
|
+
[string]
|
8
|
+
$Path
|
9
|
+
)
|
10
|
+
|
11
|
+
$userProfile = [Environment]::GetFolderPath([Environment+SpecialFolder]::UserProfile)
|
12
|
+
if ($Path -eq '~') {
|
13
|
+
$userProfile
|
14
|
+
}
|
15
|
+
elseif ($Path.StartsWith(('~\'))) {
|
16
|
+
Join-Path -Path $userProfile -ChildPath $Path.Substring(2)
|
17
|
+
}
|
18
|
+
else {
|
19
|
+
$Path
|
20
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# (c) 2025 Ansible Project
|
2
|
+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
|
4
|
+
[CmdletBinding()]
|
5
|
+
param (
|
6
|
+
[Parameter(Mandatory)]
|
7
|
+
[string]
|
8
|
+
$Directory,
|
9
|
+
|
10
|
+
[Parameter(Mandatory)]
|
11
|
+
[string]
|
12
|
+
$Name
|
13
|
+
)
|
14
|
+
|
15
|
+
$path = [Environment]::ExpandEnvironmentVariables($Directory)
|
16
|
+
$tmp = New-Item -Path $path -Name $Name -ItemType Directory
|
17
|
+
$tmp.FullName
|
@@ -25,29 +25,25 @@ import textwrap
|
|
25
25
|
import traceback
|
26
26
|
import types
|
27
27
|
import typing as t
|
28
|
+
|
28
29
|
from multiprocessing.queues import Queue
|
29
30
|
|
30
|
-
from ansible import context
|
31
31
|
from ansible._internal import _task
|
32
|
-
from ansible.
|
32
|
+
from ansible._internal._errors import _error_utils
|
33
|
+
from ansible.errors import AnsibleError
|
33
34
|
from ansible.executor.task_executor import TaskExecutor
|
34
35
|
from ansible.executor.task_queue_manager import FinalQueue, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO
|
35
36
|
from ansible.executor.task_result import _RawTaskResult
|
36
37
|
from ansible.inventory.host import Host
|
37
|
-
from ansible.module_utils.common.collections import is_sequence
|
38
38
|
from ansible.module_utils.common.text.converters import to_text
|
39
39
|
from ansible.parsing.dataloader import DataLoader
|
40
40
|
from ansible.playbook.task import Task
|
41
41
|
from ansible.playbook.play_context import PlayContext
|
42
|
-
from ansible.plugins.loader import init_plugin_loader
|
43
42
|
from ansible.utils.context_objects import CLIArgs
|
44
|
-
from ansible.plugins.action import ActionBase
|
45
43
|
from ansible.utils.display import Display
|
46
44
|
from ansible.utils.multiprocessing import context as multiprocessing_context
|
47
45
|
from ansible.vars.manager import VariableManager
|
48
46
|
|
49
|
-
from jinja2.exceptions import TemplateNotFound
|
50
|
-
|
51
47
|
__all__ = ['WorkerProcess']
|
52
48
|
|
53
49
|
display = Display()
|
@@ -204,120 +200,49 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
|
|
204
200
|
signify that they are ready for their next task.
|
205
201
|
"""
|
206
202
|
|
207
|
-
# import cProfile, pstats, StringIO
|
208
|
-
# pr = cProfile.Profile()
|
209
|
-
# pr.enable()
|
210
|
-
|
211
203
|
global current_worker
|
204
|
+
|
212
205
|
current_worker = self
|
213
206
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
207
|
+
executor_result = TaskExecutor(
|
208
|
+
self._host,
|
209
|
+
self._task,
|
210
|
+
self._task_vars,
|
211
|
+
self._play_context,
|
212
|
+
self._loader,
|
213
|
+
self._shared_loader_obj,
|
214
|
+
self._final_q,
|
215
|
+
self._variable_manager,
|
216
|
+
).run()
|
217
|
+
|
218
|
+
self._host.vars = dict()
|
219
|
+
self._host.groups = []
|
220
|
+
|
221
|
+
for name, stdio in (('stdout', sys.stdout), ('stderr', sys.stderr)):
|
222
|
+
if data := stdio.getvalue(): # type: ignore[union-attr]
|
223
|
+
display.warning(
|
224
|
+
(
|
225
|
+
f'WorkerProcess for [{self._host}/{self._task}] errantly sent data directly to {name} instead of using Display:\n'
|
226
|
+
f'{textwrap.indent(data[:256], " ")}\n'
|
227
|
+
),
|
228
|
+
formatted=True
|
229
|
+
)
|
228
230
|
|
229
231
|
try:
|
230
|
-
# execute the task and build a _RawTaskResult from the result
|
231
|
-
display.debug("running TaskExecutor() for %s/%s" % (self._host, self._task))
|
232
|
-
executor_result = TaskExecutor(
|
233
|
-
self._host,
|
234
|
-
self._task,
|
235
|
-
self._task_vars,
|
236
|
-
self._play_context,
|
237
|
-
self._loader,
|
238
|
-
self._shared_loader_obj,
|
239
|
-
self._final_q,
|
240
|
-
self._variable_manager,
|
241
|
-
).run()
|
242
|
-
|
243
|
-
display.debug("done running TaskExecutor() for %s/%s [%s]" % (self._host, self._task, self._task._uuid))
|
244
|
-
self._host.vars = dict()
|
245
|
-
self._host.groups = []
|
246
|
-
|
247
|
-
for name, stdio in (('stdout', sys.stdout), ('stderr', sys.stderr)):
|
248
|
-
if data := stdio.getvalue(): # type: ignore[union-attr]
|
249
|
-
display.warning(
|
250
|
-
(
|
251
|
-
f'WorkerProcess for [{self._host}/{self._task}] errantly sent data directly to {name} instead of using Display:\n'
|
252
|
-
f'{textwrap.indent(data[:256], " ")}\n'
|
253
|
-
),
|
254
|
-
formatted=True
|
255
|
-
)
|
256
|
-
|
257
|
-
# put the result on the result queue
|
258
|
-
display.debug("sending task result for task %s" % self._task._uuid)
|
259
|
-
try:
|
260
|
-
self._final_q.send_task_result(_RawTaskResult(
|
261
|
-
host=self._host,
|
262
|
-
task=self._task,
|
263
|
-
return_data=executor_result,
|
264
|
-
task_fields=self._task.dump_attrs(),
|
265
|
-
))
|
266
|
-
except Exception as ex:
|
267
|
-
try:
|
268
|
-
raise AnsibleError("Task result omitted due to queue send failure.") from ex
|
269
|
-
except Exception as ex_wrapper:
|
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
|
-
))
|
276
|
-
|
277
|
-
display.debug("done sending task result for task %s" % self._task._uuid)
|
278
|
-
|
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
|
-
|
284
|
-
self._host.vars = dict()
|
285
|
-
self._host.groups = []
|
286
232
|
self._final_q.send_task_result(_RawTaskResult(
|
287
233
|
host=self._host,
|
288
234
|
task=self._task,
|
289
|
-
return_data=
|
235
|
+
return_data=executor_result,
|
290
236
|
task_fields=self._task.dump_attrs(),
|
291
237
|
))
|
292
|
-
|
293
238
|
except Exception as ex:
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
self.
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
except Exception:
|
305
|
-
display.debug(u"WORKER EXCEPTION: %s" % to_text(ex))
|
306
|
-
display.debug(u"WORKER TRACEBACK: %s" % to_text(traceback.format_exc()))
|
307
|
-
finally:
|
308
|
-
self._clean_up()
|
309
|
-
|
310
|
-
display.debug("WORKER PROCESS EXITING")
|
311
|
-
|
312
|
-
# pr.disable()
|
313
|
-
# s = StringIO.StringIO()
|
314
|
-
# sortby = 'time'
|
315
|
-
# ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
|
316
|
-
# ps.print_stats()
|
317
|
-
# with open('worker_%06d.stats' % os.getpid(), 'w') as f:
|
318
|
-
# f.write(s.getvalue())
|
319
|
-
|
320
|
-
def _clean_up(self) -> None:
|
321
|
-
# NOTE: see note in init about forks
|
322
|
-
# ensure we cleanup all temp files for this worker
|
323
|
-
self._loader.cleanup_all_tmp_files()
|
239
|
+
try:
|
240
|
+
raise AnsibleError("Task result omitted due to queue send failure.") from ex
|
241
|
+
except Exception as ex_wrapper:
|
242
|
+
self._final_q.send_task_result(_RawTaskResult(
|
243
|
+
host=self._host,
|
244
|
+
task=self._task,
|
245
|
+
# ignore the real task result and don't allow result object contribution from the exception (in case the pickling error was related)
|
246
|
+
return_data=_error_utils.result_dict_from_exception(ex_wrapper),
|
247
|
+
task_fields={}, # The failure pickling may have been caused by the task attrs, omit for safety
|
248
|
+
))
|
@@ -7,7 +7,6 @@ import os
|
|
7
7
|
import time
|
8
8
|
import json
|
9
9
|
import pathlib
|
10
|
-
import signal
|
11
10
|
import subprocess
|
12
11
|
import sys
|
13
12
|
|
@@ -17,13 +16,13 @@ import typing as t
|
|
17
16
|
from ansible import constants as C
|
18
17
|
from ansible.cli import scripts
|
19
18
|
from ansible.errors import (
|
20
|
-
AnsibleError, AnsibleParserError, AnsibleUndefinedVariable,
|
19
|
+
AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleTaskError,
|
21
20
|
AnsibleValueOmittedError,
|
22
21
|
)
|
23
22
|
from ansible.executor.task_result import _RawTaskResult
|
24
23
|
from ansible._internal._datatag import _utils
|
25
|
-
from ansible.module_utils.
|
26
|
-
from ansible.module_utils.datatag import native_type_name
|
24
|
+
from ansible.module_utils._internal import _messages
|
25
|
+
from ansible.module_utils.datatag import native_type_name, deprecator_from_collection_name
|
27
26
|
from ansible._internal._datatag._tags import TrustedAsTemplate
|
28
27
|
from ansible.module_utils.parsing.convert_bool import boolean
|
29
28
|
from ansible.module_utils.common.text.converters import to_text, to_native
|
@@ -31,7 +30,6 @@ from ansible.module_utils.connection import write_to_stream
|
|
31
30
|
from ansible.module_utils.six import string_types
|
32
31
|
from ansible.playbook.task import Task
|
33
32
|
from ansible.plugins import get_plugin_class
|
34
|
-
from ansible.plugins.action import ActionBase
|
35
33
|
from ansible.plugins.loader import become_loader, cliconf_loader, connection_loader, httpapi_loader, netconf_loader, terminal_loader
|
36
34
|
from ansible._internal._templating._jinja_plugins import _invoke_lookup, _DirectCall
|
37
35
|
from ansible._internal._templating._engine import TemplateEngine
|
@@ -41,7 +39,7 @@ from ansible.utils.display import Display, _DeferredWarningContext
|
|
41
39
|
from ansible.utils.vars import combine_vars
|
42
40
|
from ansible.vars.clean import namespace_facts, clean_facts
|
43
41
|
from ansible.vars.manager import _deprecate_top_level_fact
|
44
|
-
from ansible._internal._errors import _captured
|
42
|
+
from ansible._internal._errors import _captured, _task_timeout, _error_utils
|
45
43
|
|
46
44
|
if t.TYPE_CHECKING:
|
47
45
|
from ansible.executor.task_queue_manager import FinalQueue
|
@@ -54,24 +52,6 @@ RETURN_VARS = [x for x in C.MAGIC_VARIABLE_MAPPING.items() if 'become' not in x
|
|
54
52
|
__all__ = ['TaskExecutor']
|
55
53
|
|
56
54
|
|
57
|
-
class TaskTimeoutError(BaseException):
|
58
|
-
def __init__(self, message="", frame=None):
|
59
|
-
|
60
|
-
if frame is not None:
|
61
|
-
orig = frame
|
62
|
-
root = pathlib.Path(__file__).parent
|
63
|
-
while not pathlib.Path(frame.f_code.co_filename).is_relative_to(root):
|
64
|
-
frame = frame.f_back
|
65
|
-
|
66
|
-
self.frame = 'Interrupted at %s called from %s' % (orig, frame)
|
67
|
-
|
68
|
-
super(TaskTimeoutError, self).__init__(message)
|
69
|
-
|
70
|
-
|
71
|
-
def task_timeout(signum, frame):
|
72
|
-
raise TaskTimeoutError(frame=frame)
|
73
|
-
|
74
|
-
|
75
55
|
class TaskExecutor:
|
76
56
|
|
77
57
|
"""
|
@@ -176,7 +156,7 @@ class TaskExecutor:
|
|
176
156
|
|
177
157
|
return res
|
178
158
|
except Exception as ex:
|
179
|
-
result =
|
159
|
+
result = _error_utils.result_dict_from_exception(ex)
|
180
160
|
|
181
161
|
self._task.update_result_no_log(self._task_templar, result)
|
182
162
|
|
@@ -442,11 +422,11 @@ class TaskExecutor:
|
|
442
422
|
result = self._execute_internal(templar, variables)
|
443
423
|
self._apply_task_result_compat(result, warning_ctx)
|
444
424
|
_captured.AnsibleActionCapturedError.maybe_raise_on_result(result)
|
445
|
-
except Exception as ex:
|
425
|
+
except (Exception, _task_timeout.TaskTimeoutError) as ex: # TaskTimeoutError is BaseException
|
446
426
|
try:
|
447
427
|
raise AnsibleTaskError(obj=self._task.get_ds()) from ex
|
448
428
|
except AnsibleTaskError as atex:
|
449
|
-
result =
|
429
|
+
result = _error_utils.result_dict_from_exception(atex, accept_result_contribution=True)
|
450
430
|
result.setdefault('changed', False)
|
451
431
|
|
452
432
|
self._task.update_result_no_log(templar, result)
|
@@ -530,7 +510,7 @@ class TaskExecutor:
|
|
530
510
|
# if we ran into an error while setting up the PlayContext, raise it now, unless is known issue with delegation
|
531
511
|
# and undefined vars (correct values are in cvars later on and connection plugins, if still error, blows up there)
|
532
512
|
|
533
|
-
# DTFIX-
|
513
|
+
# DTFIX-FUTURE: this should probably be declaratively handled in post_validate (or better, get rid of play_context)
|
534
514
|
if context_validation_error is not None:
|
535
515
|
raiseit = True
|
536
516
|
if self._task.delegate_to:
|
@@ -539,7 +519,7 @@ class TaskExecutor:
|
|
539
519
|
if isinstance(context_validation_error.__cause__, AnsibleUndefinedVariable):
|
540
520
|
raiseit = False
|
541
521
|
elif isinstance(context_validation_error, AnsibleUndefinedVariable):
|
542
|
-
# DTFIX-
|
522
|
+
# DTFIX-FUTURE: should not be possible to hit this now (all are AnsibleFieldAttributeError)?
|
543
523
|
raiseit = False
|
544
524
|
if raiseit:
|
545
525
|
raise context_validation_error # pylint: disable=raising-bad-type
|
@@ -636,24 +616,9 @@ class TaskExecutor:
|
|
636
616
|
for attempt in range(1, retries + 1):
|
637
617
|
display.debug("running the handler")
|
638
618
|
try:
|
639
|
-
|
640
|
-
|
641
|
-
signal.alarm(self._task.timeout)
|
642
|
-
|
643
|
-
result = self._handler.run(task_vars=vars_copy)
|
644
|
-
|
645
|
-
# DTFIX-RELEASE: nuke this, it hides a lot of error detail- remove the active exception propagation hack from AnsibleActionFail at the same time
|
646
|
-
except (AnsibleActionFail, AnsibleActionSkip) as e:
|
647
|
-
return e.result
|
648
|
-
except AnsibleConnectionFailure as e:
|
649
|
-
return dict(unreachable=True, msg=to_text(e))
|
650
|
-
except TaskTimeoutError as e:
|
651
|
-
msg = 'The %s action failed to execute in the expected time frame (%d) and was terminated' % (self._task.action, self._task.timeout)
|
652
|
-
return dict(failed=True, msg=msg, timedout={'frame': e.frame, 'period': self._task.timeout})
|
619
|
+
with _task_timeout.TaskTimeoutError.alarm_timeout(self._task.timeout):
|
620
|
+
result = self._handler.run(task_vars=vars_copy)
|
653
621
|
finally:
|
654
|
-
if self._task.timeout:
|
655
|
-
signal.alarm(0)
|
656
|
-
old_sig = signal.signal(signal.SIGALRM, old_sig)
|
657
622
|
self._handler.cleanup()
|
658
623
|
display.debug("handler run complete")
|
659
624
|
|
@@ -731,7 +696,7 @@ class TaskExecutor:
|
|
731
696
|
if 'skipped' not in result:
|
732
697
|
condname = 'changed'
|
733
698
|
|
734
|
-
# DTFIX-
|
699
|
+
# DTFIX-FUTURE: error normalization has not yet occurred; this means that the expressions used for until/failed_when/changed_when/break_when
|
735
700
|
# and when (for loops on the second and later iterations) cannot see the normalized error shapes. This, and the current impl of the expression
|
736
701
|
# handling here causes a number of problems:
|
737
702
|
# * any error in one of the post-task exec expressions is silently ignored and detail lost (eg: `failed_when: syntax ERROR @$123`)
|
@@ -825,11 +790,11 @@ class TaskExecutor:
|
|
825
790
|
if warnings := result.get('warnings'):
|
826
791
|
if isinstance(warnings, list):
|
827
792
|
for warning in warnings:
|
828
|
-
if not isinstance(warning, WarningSummary):
|
793
|
+
if not isinstance(warning, _messages.WarningSummary):
|
829
794
|
# translate non-WarningMessageDetail messages
|
830
|
-
warning = WarningSummary(
|
831
|
-
|
832
|
-
|
795
|
+
warning = _messages.WarningSummary(
|
796
|
+
event=_messages.Event(
|
797
|
+
msg=str(warning),
|
833
798
|
),
|
834
799
|
)
|
835
800
|
|
@@ -840,18 +805,18 @@ class TaskExecutor:
|
|
840
805
|
if deprecations := result.get('deprecations'):
|
841
806
|
if isinstance(deprecations, list):
|
842
807
|
for deprecation in deprecations:
|
843
|
-
if not isinstance(deprecation, DeprecationSummary):
|
844
|
-
# translate non-
|
808
|
+
if not isinstance(deprecation, _messages.DeprecationSummary):
|
809
|
+
# translate non-DeprecationSummary message dicts
|
845
810
|
try:
|
846
811
|
if (collection_name := deprecation.pop('collection_name', ...)) is not ...:
|
847
812
|
# deprecated: description='enable the deprecation message for collection_name' core_version='2.23'
|
848
813
|
# CAUTION: This deprecation cannot be enabled until the replacement (deprecator) has been documented, and the schema finalized.
|
849
814
|
# self.deprecated('The `collection_name` key in the `deprecations` dictionary is deprecated.', version='2.27')
|
850
|
-
deprecation.update(deprecator=
|
815
|
+
deprecation.update(deprecator=deprecator_from_collection_name(collection_name))
|
851
816
|
|
852
|
-
deprecation = DeprecationSummary(
|
853
|
-
|
854
|
-
|
817
|
+
deprecation = _messages.DeprecationSummary(
|
818
|
+
event=_messages.Event(
|
819
|
+
msg=deprecation.pop('msg'),
|
855
820
|
),
|
856
821
|
**deprecation,
|
857
822
|
)
|
@@ -910,9 +875,9 @@ class TaskExecutor:
|
|
910
875
|
# have issues which result in a half-written/unparseable result
|
911
876
|
# file on disk, which manifests to the user as a timeout happening
|
912
877
|
# before it's time to timeout.
|
913
|
-
if (
|
914
|
-
|
915
|
-
|
878
|
+
if (async_result.get('finished', False) or
|
879
|
+
(async_result.get('failed', False) and async_result.get('_ansible_parsed', False)) or
|
880
|
+
async_result.get('skipped', False)):
|
916
881
|
break
|
917
882
|
except Exception as e:
|
918
883
|
# Connections can raise exceptions during polling (eg, network bounce, reboot); these should be non-fatal.
|
@@ -941,7 +906,7 @@ class TaskExecutor:
|
|
941
906
|
),
|
942
907
|
)
|
943
908
|
|
944
|
-
if
|
909
|
+
if not async_result.get('finished', False):
|
945
910
|
if async_result.get('_ansible_parsed'):
|
946
911
|
return dict(failed=True, msg="async task did not complete within the requested time - %ss" % self._task.async_val, async_result=async_result)
|
947
912
|
else:
|
ansible/executor/task_result.py
CHANGED
@@ -12,7 +12,7 @@ import typing as t
|
|
12
12
|
from ansible import constants
|
13
13
|
from ansible.utils import vars as _vars
|
14
14
|
from ansible.vars.clean import module_response_deepcopy, strip_internal_keys
|
15
|
-
from ansible.module_utils.
|
15
|
+
from ansible.module_utils._internal import _messages
|
16
16
|
from ansible._internal import _collection_proxy
|
17
17
|
|
18
18
|
if t.TYPE_CHECKING:
|
@@ -20,7 +20,7 @@ if t.TYPE_CHECKING:
|
|
20
20
|
from ansible.playbook.task import Task
|
21
21
|
|
22
22
|
_IGNORE = ('failed', 'skipped')
|
23
|
-
_PRESERVE = {'attempts', 'changed', 'retries', '_ansible_no_log'}
|
23
|
+
_PRESERVE = {'attempts', 'changed', 'retries', '_ansible_no_log', 'exception', 'warnings', 'deprecations'}
|
24
24
|
_SUB_PRESERVE = {'_ansible_delegated_vars': {'ansible_host', 'ansible_port', 'ansible_user', 'ansible_connection'}}
|
25
25
|
|
26
26
|
# stuff callbacks need
|
@@ -230,8 +230,6 @@ class _RawTaskResult(_BaseTaskResult):
|
|
230
230
|
class CallbackTaskResult(_BaseTaskResult):
|
231
231
|
"""Public contract of TaskResult """
|
232
232
|
|
233
|
-
# DTFIX-RELEASE: find a better home for this since it's public API
|
234
|
-
|
235
233
|
@property
|
236
234
|
def _result(self) -> _c.MutableMapping[str, t.Any]:
|
237
235
|
"""Use the `result` property when supporting only ansible-core 2.19 or later."""
|
@@ -31,6 +31,7 @@ from dataclasses import dataclass
|
|
31
31
|
from hashlib import sha256
|
32
32
|
from io import BytesIO
|
33
33
|
from importlib.metadata import distribution
|
34
|
+
from importlib.resources import files
|
34
35
|
from itertools import chain
|
35
36
|
|
36
37
|
try:
|
@@ -85,7 +86,6 @@ if t.TYPE_CHECKING:
|
|
85
86
|
FilesManifestType = t.Dict[t.Literal['files', 'format'], t.Union[t.List[FileManifestEntryType], int]]
|
86
87
|
|
87
88
|
import ansible.constants as C
|
88
|
-
from ansible.compat.importlib_resources import files
|
89
89
|
from ansible.errors import AnsibleError
|
90
90
|
from ansible.galaxy.api import GalaxyAPI
|
91
91
|
from ansible.galaxy.collection.concrete_artifact_manager import (
|
@@ -1433,9 +1433,6 @@ def find_existing_collections(path_filter, artifacts_manager, namespace_filter=N
|
|
1433
1433
|
:param path: Collection dirs layout search path.
|
1434
1434
|
:param artifacts_manager: Artifacts manager.
|
1435
1435
|
"""
|
1436
|
-
if files is None:
|
1437
|
-
raise AnsibleError('importlib_resources is not installed and is required')
|
1438
|
-
|
1439
1436
|
if path_filter and not is_sequence(path_filter):
|
1440
1437
|
path_filter = [path_filter]
|
1441
1438
|
if namespace_filter and not is_sequence(namespace_filter):
|
ansible/inventory/manager.py
CHANGED
@@ -313,6 +313,7 @@ class InventoryManager(object):
|
|
313
313
|
ex.obj = origin
|
314
314
|
failures.append({'src': source, 'plugin': plugin_name, 'exc': ex})
|
315
315
|
except Exception as ex:
|
316
|
+
# DTFIX-FUTURE: fix this error handling to correctly deal with messaging
|
316
317
|
try:
|
317
318
|
# omit line number to prevent contextual display of script or possibly sensitive info
|
318
319
|
raise AnsibleError(str(ex), obj=origin) from ex
|
@@ -5,7 +5,6 @@ import collections.abc as c
|
|
5
5
|
import typing as t
|
6
6
|
|
7
7
|
|
8
|
-
# DTFIX-RELEASE: bikeshed "intermediate"
|
9
8
|
INTERMEDIATE_MAPPING_TYPES = (c.Mapping,)
|
10
9
|
"""
|
11
10
|
Mapping types which are supported for recursion and runtime usage, such as in serialization and templating.
|
@@ -25,13 +24,11 @@ ITERABLE_SCALARS_NOT_TO_ITERATE_FIXME = (str, bytes)
|
|
25
24
|
|
26
25
|
def is_intermediate_mapping(value: object) -> bool:
|
27
26
|
"""Returns `True` if `value` is a type supported for projection to a Python `dict`, otherwise returns `False`."""
|
28
|
-
# DTFIX-RELEASE: bikeshed name
|
29
27
|
return isinstance(value, INTERMEDIATE_MAPPING_TYPES)
|
30
28
|
|
31
29
|
|
32
30
|
def is_intermediate_iterable(value: object) -> bool:
|
33
31
|
"""Returns `True` if `value` is a type supported for projection to a Python `list`, otherwise returns `False`."""
|
34
|
-
# DTFIX-RELEASE: bikeshed name
|
35
32
|
return isinstance(value, INTERMEDIATE_ITERABLE_TYPES) and not isinstance(value, ITERABLE_SCALARS_NOT_TO_ITERATE_FIXME)
|
36
33
|
|
37
34
|
|
@@ -19,9 +19,9 @@ class AmbientContextBase:
|
|
19
19
|
__slots__ = ('_contextvar_token',)
|
20
20
|
|
21
21
|
# DTFIX-FUTURE: subclasses need to be able to opt-in to blocking nested contexts of the same type (basically optional per-callstack singleton behavior)
|
22
|
-
# DTFIX-
|
22
|
+
# DTFIX-FUTURE: this class should enforce strict nesting of contexts; overlapping context lifetimes leads to incredibly difficult to
|
23
23
|
# debug situations with undefined behavior, so it should fail fast.
|
24
|
-
# DTFIX-
|
24
|
+
# DTFIX-FUTURE: make frozen=True dataclass subclasses work (fix the mutability of the contextvar instance)
|
25
25
|
|
26
26
|
_contextvar: t.ClassVar[contextvars.ContextVar] # pylint: disable=declare-non-slot # pylint bug, see https://github.com/pylint-dev/pylint/issues/9950
|
27
27
|
_contextvar_token: contextvars.Token
|
@@ -49,7 +49,7 @@ class AmbientContextBase:
|
|
49
49
|
raise ReferenceError(f"A required {cls.__name__} context is not active.") from None
|
50
50
|
|
51
51
|
def __enter__(self) -> t.Self:
|
52
|
-
# DTFIX-
|
52
|
+
# DTFIX-FUTURE: actively block multiple entry
|
53
53
|
self._contextvar_token = self.__class__._contextvar.set(self)
|
54
54
|
return self
|
55
55
|
|
@@ -13,7 +13,7 @@ import runpy
|
|
13
13
|
import sys
|
14
14
|
import typing as t
|
15
15
|
|
16
|
-
from . import _errors
|
16
|
+
from . import _errors, _traceback, _messages
|
17
17
|
from .. import basic
|
18
18
|
from ..common.json import get_module_encoder, Direction
|
19
19
|
|
@@ -97,7 +97,9 @@ def _handle_exception(exception: BaseException, profile: str) -> t.NoReturn:
|
|
97
97
|
"""Handle the given exception."""
|
98
98
|
result = dict(
|
99
99
|
failed=True,
|
100
|
-
exception=
|
100
|
+
exception=_messages.ErrorSummary(
|
101
|
+
event=_errors.EventFactory.from_exception(exception, _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR)),
|
102
|
+
),
|
101
103
|
)
|
102
104
|
|
103
105
|
encoder = get_module_encoder(profile, Direction.MODULE_TO_CONTROLLER)
|