ansible-core 2.19.0b6__py3-none-any.whl → 2.19.0b7__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/_templating/_jinja_bits.py +16 -1
- ansible/_internal/_templating/_jinja_common.py +1 -1
- ansible/cli/__init__.py +2 -2
- ansible/cli/adhoc.py +6 -3
- ansible/cli/console.py +1 -1
- ansible/cli/doc.py +2 -2
- ansible/config/base.yml +9 -6
- ansible/executor/module_common.py +8 -5
- ansible/executor/powershell/psrp_put_file.ps1 +1 -1
- ansible/executor/task_executor.py +2 -2
- ansible/executor/task_queue_manager.py +34 -70
- ansible/executor/task_result.py +1 -1
- ansible/galaxy/api.py +2 -2
- ansible/galaxy/collection/concrete_artifact_manager.py +2 -2
- ansible/galaxy/dependency_resolution/providers.py +3 -3
- ansible/inventory/group.py +6 -1
- ansible/inventory/host.py +6 -1
- ansible/module_utils/_internal/_deprecator.py +12 -1
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +14 -16
- ansible/module_utils/common/yaml.py +1 -1
- ansible/module_utils/csharp/Ansible.Basic.cs +1 -1
- ansible/module_utils/csharp/Ansible.Privilege.cs +2 -2
- ansible/module_utils/facts/hardware/base.py +1 -1
- ansible/module_utils/facts/other/facter.py +1 -1
- ansible/module_utils/facts/system/distribution.py +2 -2
- ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1 +1 -1
- ansible/module_utils/urls.py +1 -1
- ansible/modules/apt.py +9 -3
- ansible/modules/assemble.py +5 -3
- ansible/modules/expect.py +5 -5
- ansible/modules/hostname.py +2 -2
- ansible/modules/pip.py +9 -11
- ansible/modules/raw.py +2 -2
- ansible/modules/stat.py +1 -1
- ansible/modules/wait_for.py +10 -3
- ansible/parsing/mod_args.py +38 -20
- ansible/parsing/vault/__init__.py +3 -3
- ansible/playbook/base.py +0 -2
- ansible/playbook/helpers.py +1 -1
- ansible/playbook/playbook_include.py +23 -56
- ansible/playbook/role/__init__.py +38 -21
- ansible/plugins/action/__init__.py +2 -2
- ansible/plugins/action/assemble.py +2 -1
- ansible/plugins/action/assert.py +2 -2
- ansible/plugins/action/script.py +5 -4
- ansible/plugins/action/template.py +1 -1
- ansible/plugins/callback/__init__.py +77 -87
- ansible/plugins/callback/default.py +0 -3
- ansible/plugins/callback/junit.py +0 -6
- ansible/plugins/connection/ssh.py +1 -1
- ansible/plugins/filter/pow.yml +1 -1
- ansible/plugins/filter/root.yml +1 -1
- ansible/plugins/filter/strftime.yml +3 -3
- ansible/plugins/filter/to_uuid.yml +1 -1
- ansible/plugins/inventory/script.py +1 -1
- ansible/plugins/loader.py +5 -0
- ansible/plugins/lookup/password.py +4 -6
- ansible/release.py +1 -1
- ansible/utils/display.py +16 -26
- ansible/utils/path.py +1 -1
- ansible/utils/vars.py +4 -1
- ansible/vars/manager.py +6 -3
- ansible/vars/reserved.py +6 -4
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/RECORD +101 -99
- ansible_test/_internal/__init__.py +5 -0
- ansible_test/_internal/ansible_util.py +1 -1
- ansible_test/_internal/classification/python.py +6 -0
- ansible_test/_internal/cli/commands/__init__.py +0 -5
- ansible_test/_internal/cli/environments.py +51 -5
- ansible_test/_internal/commands/coverage/__init__.py +1 -1
- ansible_test/_internal/commands/integration/__init__.py +18 -5
- ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
- ansible_test/_internal/commands/sanity/__init__.py +3 -1
- ansible_test/_internal/commands/sanity/integration_aliases.py +11 -0
- ansible_test/_internal/commands/shell/__init__.py +43 -4
- ansible_test/_internal/commands/units/__init__.py +4 -1
- ansible_test/_internal/config.py +21 -13
- ansible_test/_internal/debugging.py +166 -0
- ansible_test/_internal/delegation.py +21 -13
- ansible_test/_internal/host_profiles.py +197 -6
- ansible_test/_internal/inventory.py +4 -0
- ansible_test/_internal/metadata.py +94 -4
- ansible_test/_internal/processes.py +80 -0
- ansible_test/_internal/python_requirements.py +27 -0
- ansible_test/_internal/target.py +8 -0
- ansible_test/_internal/util_common.py +13 -3
- ansible_test/_util/target/injector/python.py +8 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/top_level.txt +0 -0
@@ -78,6 +78,21 @@ The values following this prefix up to the first newline are parsed as Jinja2 te
|
|
78
78
|
To include this literal value at the start of a string, a space or other character must precede it.
|
79
79
|
"""
|
80
80
|
|
81
|
+
JINJA_KEYWORDS = frozenset(
|
82
|
+
{
|
83
|
+
# scalar singletons (see jinja2.nodes.Name.can_assign)
|
84
|
+
'true',
|
85
|
+
'false',
|
86
|
+
'none',
|
87
|
+
'True',
|
88
|
+
'False',
|
89
|
+
'None',
|
90
|
+
# other
|
91
|
+
'not', # unary operator always applicable to names
|
92
|
+
}
|
93
|
+
)
|
94
|
+
"""Names which have special meaning to Jinja and cannot be resolved as variable names."""
|
95
|
+
|
81
96
|
display = Display()
|
82
97
|
|
83
98
|
|
@@ -799,7 +814,7 @@ class AnsibleEnvironment(ImmutableSandboxedEnvironment):
|
|
799
814
|
# Performing either before calling them will interfere with that processing.
|
800
815
|
return super().call(__context, __obj, *args, **kwargs)
|
801
816
|
|
802
|
-
# Jinja's generated macro code handles Markers, so
|
817
|
+
# Jinja's generated macro code handles Markers, so preemptive raise on Marker args and lazy retrieval should be disabled for the macro invocation.
|
803
818
|
is_macro = isinstance(__obj, Macro)
|
804
819
|
|
805
820
|
if not is_macro and (first_marker := get_first_marker_arg(args, kwargs)) is not None:
|
@@ -96,7 +96,7 @@ class Marker(StrictUndefined, Tripwire):
|
|
96
96
|
return AnsibleUndefinedVariable(self._undefined_message, obj=self._marker_template_source)
|
97
97
|
|
98
98
|
def _as_message(self) -> str:
|
99
|
-
"""Return the error message to show when this marker must be represented as a string, such as for
|
99
|
+
"""Return the error message to show when this marker must be represented as a string, such as for substitutions or warnings."""
|
100
100
|
return self._undefined_message
|
101
101
|
|
102
102
|
def _fail_with_undefined_error(self, *args: t.Any, **kwargs: t.Any) -> t.NoReturn:
|
ansible/cli/__init__.py
CHANGED
@@ -212,9 +212,9 @@ class CLI(ABC):
|
|
212
212
|
# used by --vault-id and --vault-password-file
|
213
213
|
vault_ids.append(id_slug)
|
214
214
|
|
215
|
-
# if an action needs an encrypt password (create_new_password=True) and we
|
215
|
+
# if an action needs an encrypt password (create_new_password=True) and we don't
|
216
216
|
# have other secrets setup, then automatically add a password prompt as well.
|
217
|
-
# prompts
|
217
|
+
# prompts can't/shouldn't work without a tty, so don't add prompt secrets
|
218
218
|
if ask_vault_pass or (not vault_ids and auto_prompt):
|
219
219
|
|
220
220
|
id_slug = u'%s@%s' % (C.DEFAULT_VAULT_IDENTITY, u'prompt_ask_vault_pass')
|
ansible/cli/adhoc.py
CHANGED
@@ -88,8 +88,11 @@ class AdHocCLI(CLI):
|
|
88
88
|
if not module_args:
|
89
89
|
module_args = parse_kv(module_args_raw, check_raw=check_raw)
|
90
90
|
|
91
|
-
mytask =
|
92
|
-
|
91
|
+
mytask = dict(
|
92
|
+
action=context.CLIARGS['module_name'],
|
93
|
+
args=module_args,
|
94
|
+
timeout=context.CLIARGS['task_timeout'],
|
95
|
+
)
|
93
96
|
|
94
97
|
mytask = Origin(description=f'<adhoc {context.CLIARGS["module_name"]!r} task>').tag(mytask)
|
95
98
|
|
@@ -184,7 +187,7 @@ class AdHocCLI(CLI):
|
|
184
187
|
variable_manager=variable_manager,
|
185
188
|
loader=loader,
|
186
189
|
passwords=passwords,
|
187
|
-
|
190
|
+
stdout_callback_name=cb,
|
188
191
|
run_additional_callbacks=C.DEFAULT_LOAD_CALLBACK_PLUGINS,
|
189
192
|
run_tree=run_tree,
|
190
193
|
forks=context.CLIARGS['forks'],
|
ansible/cli/console.py
CHANGED
@@ -222,7 +222,7 @@ class ConsoleCLI(CLI, cmd.Cmd):
|
|
222
222
|
variable_manager=self.variable_manager,
|
223
223
|
loader=self.loader,
|
224
224
|
passwords=self.passwords,
|
225
|
-
|
225
|
+
stdout_callback_name=cb,
|
226
226
|
run_additional_callbacks=C.DEFAULT_LOAD_CALLBACK_PLUGINS,
|
227
227
|
run_tree=False,
|
228
228
|
forks=self.forks,
|
ansible/cli/doc.py
CHANGED
@@ -1309,7 +1309,7 @@ class DocCLI(CLI, RoleMixin):
|
|
1309
1309
|
if ignore in item:
|
1310
1310
|
del item[ignore]
|
1311
1311
|
|
1312
|
-
# reformat cli
|
1312
|
+
# reformat cli options
|
1313
1313
|
if 'cli' in opt and opt['cli']:
|
1314
1314
|
conf['cli'] = []
|
1315
1315
|
for cli in opt['cli']:
|
@@ -1440,7 +1440,7 @@ class DocCLI(CLI, RoleMixin):
|
|
1440
1440
|
pad = display.columns * 0.20
|
1441
1441
|
limit = max(display.columns - int(pad), 70)
|
1442
1442
|
|
1443
|
-
text.append("> %s %s (%s)" % (plugin_type.upper(), _format(doc.pop('plugin_name'), 'bold'), doc.pop('filename')))
|
1443
|
+
text.append("> %s %s (%s)" % (plugin_type.upper(), _format(doc.pop('plugin_name'), 'bold'), doc.pop('filename') or 'Jinja2'))
|
1444
1444
|
|
1445
1445
|
if isinstance(doc['description'], list):
|
1446
1446
|
descs = doc.pop('description')
|
ansible/config/base.yml
CHANGED
@@ -41,6 +41,15 @@ _CALLBACK_DISPATCH_ERROR_BEHAVIOR:
|
|
41
41
|
ignore: just continue silently
|
42
42
|
env: [ { name: _ANSIBLE_CALLBACK_DISPATCH_ERROR_BEHAVIOR } ]
|
43
43
|
version_added: '2.19'
|
44
|
+
_MODULE_METADATA:
|
45
|
+
name: Enable experimental module metadata
|
46
|
+
description:
|
47
|
+
- Enables experimental module-level metadata controls for serialization profile selection.
|
48
|
+
- This is for internal use only.
|
49
|
+
type: boolean
|
50
|
+
default: false
|
51
|
+
env: [ { name: _ANSIBLE_MODULE_METADATA } ]
|
52
|
+
version_added: '2.19'
|
44
53
|
ALLOW_BROKEN_CONDITIONALS:
|
45
54
|
# This config option will be deprecated once it no longer has any effect (2.23).
|
46
55
|
name: Allow broken conditionals
|
@@ -2176,12 +2185,6 @@ WIN_ASYNC_STARTUP_TIMEOUT:
|
|
2176
2185
|
vars:
|
2177
2186
|
- {name: ansible_win_async_startup_timeout}
|
2178
2187
|
version_added: '2.10'
|
2179
|
-
WRAP_STDERR:
|
2180
|
-
description: Control line-wrapping behavior on console warnings and errors from default output callbacks (eases pattern-based output testing)
|
2181
|
-
env: [{name: ANSIBLE_WRAP_STDERR}]
|
2182
|
-
default: false
|
2183
|
-
type: bool
|
2184
|
-
version_added: "2.19"
|
2185
2188
|
YAML_FILENAME_EXTENSIONS:
|
2186
2189
|
name: Valid YAML extensions
|
2187
2190
|
default: [".yml", ".yaml", ".json"]
|
@@ -364,7 +364,7 @@ def _get_shebang(interpreter, task_vars, templar: _template.Templar, args=tuple(
|
|
364
364
|
options=TemplateOptions(value_for_omit=None))
|
365
365
|
|
366
366
|
if not interpreter_out:
|
367
|
-
# nothing matched(None) or in case someone configures empty string or empty
|
367
|
+
# nothing matched(None) or in case someone configures empty string or empty interpreter
|
368
368
|
interpreter_out = interpreter
|
369
369
|
|
370
370
|
# set shebang
|
@@ -659,9 +659,14 @@ metadata_versions: dict[t.Any, type[ModuleMetadata]] = {
|
|
659
659
|
1: ModuleMetadataV1,
|
660
660
|
}
|
661
661
|
|
662
|
+
_DEFAULT_LEGACY_METADATA = ModuleMetadataV1(serialization_profile='legacy')
|
663
|
+
|
662
664
|
|
663
665
|
def _get_module_metadata(module: ast.Module) -> ModuleMetadata:
|
664
|
-
#
|
666
|
+
# experimental module metadata; off by default
|
667
|
+
if not C.config.get_config_value('_MODULE_METADATA'):
|
668
|
+
return _DEFAULT_LEGACY_METADATA
|
669
|
+
|
665
670
|
metadata_nodes: list[ast.Assign] = []
|
666
671
|
|
667
672
|
for node in module.body:
|
@@ -674,9 +679,7 @@ def _get_module_metadata(module: ast.Module) -> ModuleMetadata:
|
|
674
679
|
metadata_nodes.append(node)
|
675
680
|
|
676
681
|
if not metadata_nodes:
|
677
|
-
return
|
678
|
-
serialization_profile='legacy',
|
679
|
-
)
|
682
|
+
return _DEFAULT_LEGACY_METADATA
|
680
683
|
|
681
684
|
if len(metadata_nodes) > 1:
|
682
685
|
raise ValueError('Module METADATA must defined only once.')
|
@@ -102,7 +102,7 @@ begin {
|
|
102
102
|
Set-Property 'MaximumAllowedMemory' $null
|
103
103
|
}
|
104
104
|
catch {
|
105
|
-
#
|
105
|
+
# Satisfy pslint, we purposefully ignore this error as it is not critical it works.
|
106
106
|
$null = $null
|
107
107
|
}
|
108
108
|
}
|
@@ -872,7 +872,7 @@ class TaskExecutor:
|
|
872
872
|
async_result = async_handler.run(task_vars=task_vars)
|
873
873
|
# We do not bail out of the loop in cases where the failure
|
874
874
|
# is associated with a parsing error. The async_runner can
|
875
|
-
# have issues which result in a half-written/
|
875
|
+
# have issues which result in a half-written/unparsable result
|
876
876
|
# file on disk, which manifests to the user as a timeout happening
|
877
877
|
# before it's time to timeout.
|
878
878
|
if (async_result.get('finished', False) or
|
@@ -910,7 +910,7 @@ class TaskExecutor:
|
|
910
910
|
if async_result.get('_ansible_parsed'):
|
911
911
|
return dict(failed=True, msg="async task did not complete within the requested time - %ss" % self._task.async_val, async_result=async_result)
|
912
912
|
else:
|
913
|
-
return dict(failed=True, msg="async task produced
|
913
|
+
return dict(failed=True, msg="async task produced unparsable results", async_result=async_result)
|
914
914
|
else:
|
915
915
|
# If the async task finished, automatically cleanup the temporary
|
916
916
|
# status file left behind.
|
@@ -34,7 +34,6 @@ from ansible.executor.play_iterator import PlayIterator
|
|
34
34
|
from ansible.executor.stats import AggregateStats
|
35
35
|
from ansible.executor.task_result import _RawTaskResult, _WireTaskResult
|
36
36
|
from ansible.inventory.data import InventoryData
|
37
|
-
from ansible.module_utils.six import string_types
|
38
37
|
from ansible.module_utils.common.text.converters import to_native
|
39
38
|
from ansible.parsing.dataloader import DataLoader
|
40
39
|
from ansible.playbook.play_context import PlayContext
|
@@ -139,7 +138,7 @@ class TaskQueueManager:
|
|
139
138
|
variable_manager: VariableManager,
|
140
139
|
loader: DataLoader,
|
141
140
|
passwords: dict[str, str | None],
|
142
|
-
|
141
|
+
stdout_callback_name: str | None = None,
|
143
142
|
run_additional_callbacks: bool = True,
|
144
143
|
run_tree: bool = False,
|
145
144
|
forks: int | None = None,
|
@@ -149,12 +148,11 @@ class TaskQueueManager:
|
|
149
148
|
self._loader = loader
|
150
149
|
self._stats = AggregateStats()
|
151
150
|
self.passwords = passwords
|
152
|
-
self.
|
151
|
+
self._stdout_callback_name: str | None = stdout_callback_name or C.DEFAULT_STDOUT_CALLBACK
|
153
152
|
self._run_additional_callbacks = run_additional_callbacks
|
154
153
|
self._run_tree = run_tree
|
155
154
|
self._forks = forks or 5
|
156
155
|
|
157
|
-
self._callbacks_loaded = False
|
158
156
|
self._callback_plugins: list[CallbackBase] = []
|
159
157
|
self._start_at_done = False
|
160
158
|
|
@@ -199,44 +197,40 @@ class TaskQueueManager:
|
|
199
197
|
only one such callback plugin will be loaded.
|
200
198
|
"""
|
201
199
|
|
202
|
-
if self.
|
200
|
+
if self._callback_plugins:
|
203
201
|
return
|
204
202
|
|
205
|
-
|
206
|
-
|
207
|
-
self._stdout_callback = C.DEFAULT_STDOUT_CALLBACK
|
203
|
+
if not self._stdout_callback_name:
|
204
|
+
raise AnsibleError("No stdout callback name provided.")
|
208
205
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
else:
|
219
|
-
raise AnsibleError("callback must be an instance of CallbackBase or the name of a callback plugin")
|
206
|
+
stdout_callback = callback_loader.get(self._stdout_callback_name)
|
207
|
+
|
208
|
+
if not stdout_callback:
|
209
|
+
raise AnsibleError(f"Could not load {self._stdout_callback_name!r} callback plugin.")
|
210
|
+
|
211
|
+
stdout_callback._init_callback_methods()
|
212
|
+
stdout_callback.set_options()
|
213
|
+
|
214
|
+
self._callback_plugins.append(stdout_callback)
|
220
215
|
|
221
216
|
# get all configured loadable callbacks (adjacent, builtin)
|
222
|
-
|
217
|
+
plugin_types = {plugin_type.ansible_name: plugin_type for plugin_type in callback_loader.all(class_only=True)}
|
223
218
|
|
224
219
|
# add enabled callbacks that refer to collections, which might not appear in normal listing
|
225
220
|
for c in C.CALLBACKS_ENABLED:
|
226
221
|
# load all, as collection ones might be using short/redirected names and not a fqcn
|
227
222
|
plugin = callback_loader.get(c, class_only=True)
|
228
223
|
|
229
|
-
# TODO: check if this skip is redundant, loader should handle bad file/plugin cases already
|
230
224
|
if plugin:
|
231
225
|
# avoids incorrect and dupes possible due to collections
|
232
|
-
|
233
|
-
callback_list.append(plugin)
|
226
|
+
plugin_types.setdefault(plugin.ansible_name, plugin)
|
234
227
|
else:
|
235
228
|
display.warning("Skipping callback plugin '%s', unable to load" % c)
|
236
229
|
|
237
|
-
|
238
|
-
for callback_plugin in callback_list:
|
230
|
+
plugin_types.pop(stdout_callback.ansible_name, None)
|
239
231
|
|
232
|
+
# for each callback in the list see if we should add it to 'active callbacks' used in the play
|
233
|
+
for callback_plugin in plugin_types.values():
|
240
234
|
callback_type = getattr(callback_plugin, 'CALLBACK_TYPE', '')
|
241
235
|
callback_needs_enabled = getattr(callback_plugin, 'CALLBACK_NEEDS_ENABLED', getattr(callback_plugin, 'CALLBACK_NEEDS_WHITELIST', False))
|
242
236
|
|
@@ -252,10 +246,8 @@ class TaskQueueManager:
|
|
252
246
|
display.vvvvv("Attempting to use '%s' callback." % (callback_name))
|
253
247
|
if callback_type == 'stdout':
|
254
248
|
# we only allow one callback of type 'stdout' to be loaded,
|
255
|
-
|
256
|
-
|
257
|
-
continue
|
258
|
-
stdout_callback_loaded = True
|
249
|
+
display.vv("Skipping callback '%s', as we already have a stdout callback." % (callback_name))
|
250
|
+
continue
|
259
251
|
elif callback_name == 'tree' and self._run_tree:
|
260
252
|
# TODO: remove special case for tree, which is an adhoc cli option --tree
|
261
253
|
pass
|
@@ -270,21 +262,16 @@ class TaskQueueManager:
|
|
270
262
|
# avoid bad plugin not returning an object, only needed cause we do class_only load and bypass loader checks,
|
271
263
|
# really a bug in the plugin itself which we ignore as callback errors are not supposed to be fatal.
|
272
264
|
if callback_obj:
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
self._callback_plugins.append(callback_obj)
|
277
|
-
else:
|
278
|
-
display.vv("Skipping callback '%s', already loaded as '%s'." % (callback_plugin, callback_name))
|
265
|
+
callback_obj._init_callback_methods()
|
266
|
+
callback_obj.set_options()
|
267
|
+
self._callback_plugins.append(callback_obj)
|
279
268
|
else:
|
280
269
|
display.warning("Skipping callback '%s', as it does not create a valid plugin instance." % callback_name)
|
281
270
|
continue
|
282
|
-
except Exception as
|
283
|
-
display.
|
271
|
+
except Exception as ex:
|
272
|
+
display.warning_as_error(f"Failed to load callback plugin {callback_name!r}.", exception=ex)
|
284
273
|
continue
|
285
274
|
|
286
|
-
self._callbacks_loaded = True
|
287
|
-
|
288
275
|
def run(self, play):
|
289
276
|
"""
|
290
277
|
Iterates over the roles/tasks in a play, using the given (or default)
|
@@ -294,8 +281,7 @@ class TaskQueueManager:
|
|
294
281
|
are done with the current task).
|
295
282
|
"""
|
296
283
|
|
297
|
-
|
298
|
-
self.load_callbacks()
|
284
|
+
self.load_callbacks()
|
299
285
|
|
300
286
|
all_vars = self._variable_manager.get_vars(play=play)
|
301
287
|
templar = TemplateEngine(loader=self._loader, variables=all_vars)
|
@@ -311,13 +297,9 @@ class TaskQueueManager:
|
|
311
297
|
)
|
312
298
|
|
313
299
|
play_context = PlayContext(new_play, self.passwords, self._connection_lockfile.fileno())
|
314
|
-
if (self._stdout_callback and
|
315
|
-
hasattr(self._stdout_callback, 'set_play_context')):
|
316
|
-
self._stdout_callback.set_play_context(play_context)
|
317
300
|
|
318
301
|
for callback_plugin in self._callback_plugins:
|
319
|
-
|
320
|
-
callback_plugin.set_play_context(play_context)
|
302
|
+
callback_plugin.set_play_context(play_context)
|
321
303
|
|
322
304
|
self.send_callback('v2_playbook_on_play_start', new_play)
|
323
305
|
|
@@ -437,7 +419,7 @@ class TaskQueueManager:
|
|
437
419
|
@lock_decorator(attr='_callback_lock')
|
438
420
|
def send_callback(self, method_name, *args, **kwargs):
|
439
421
|
# We always send events to stdout callback first, rest should follow config order
|
440
|
-
for callback_plugin in
|
422
|
+
for callback_plugin in self._callback_plugins:
|
441
423
|
# a plugin that set self.disabled to True will not be called
|
442
424
|
# see osx_say.py example for such a plugin
|
443
425
|
if callback_plugin.disabled:
|
@@ -448,31 +430,13 @@ class TaskQueueManager:
|
|
448
430
|
if not callback_plugin.wants_implicit_tasks and (task_arg := self._first_arg_of_type(Task, args)) and task_arg.implicit:
|
449
431
|
continue
|
450
432
|
|
451
|
-
# try to find v2 method, fallback to v1 method, ignore callback if no method found
|
452
433
|
methods = []
|
453
434
|
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
if method is None:
|
458
|
-
method = getattr(callback_plugin, possible.removeprefix('v2_'), None)
|
459
|
-
|
460
|
-
if method is not None:
|
461
|
-
display.deprecated(
|
462
|
-
msg='The v1 callback API is deprecated.',
|
463
|
-
version='2.23',
|
464
|
-
help_text='Use `v2_` prefixed callback methods instead.',
|
465
|
-
)
|
466
|
-
|
467
|
-
if method is not None and not getattr(method, '_base_impl', False): # don't bother dispatching to the base impls
|
468
|
-
if possible == 'v2_on_any':
|
469
|
-
display.deprecated(
|
470
|
-
msg='The `v2_on_any` callback method is deprecated.',
|
471
|
-
version='2.23',
|
472
|
-
help_text='Use event-specific callback methods instead.',
|
473
|
-
)
|
435
|
+
if method_name in callback_plugin._implemented_callback_methods:
|
436
|
+
methods.append(getattr(callback_plugin, method_name))
|
474
437
|
|
475
|
-
|
438
|
+
if 'v2_on_any' in callback_plugin._implemented_callback_methods:
|
439
|
+
methods.append(getattr(callback_plugin, 'v2_on_any'))
|
476
440
|
|
477
441
|
for method in methods:
|
478
442
|
# send clean copies
|
@@ -498,4 +462,4 @@ class TaskQueueManager:
|
|
498
462
|
except Exception as ex:
|
499
463
|
raise AnsibleCallbackError(f"Callback dispatch {method_name!r} failed for plugin {callback_plugin._load_name!r}.") from ex
|
500
464
|
|
501
|
-
callback_plugin._current_task_result = None
|
465
|
+
callback_plugin._current_task_result = None # clear temporary instance storage hack
|
ansible/executor/task_result.py
CHANGED
@@ -27,7 +27,7 @@ _SUB_PRESERVE = {'_ansible_delegated_vars': {'ansible_host', 'ansible_port', 'an
|
|
27
27
|
CLEAN_EXCEPTIONS = (
|
28
28
|
'_ansible_verbose_always', # for debug and other actions, to always expand data (pretty jsonification)
|
29
29
|
'_ansible_item_label', # to know actual 'item' variable
|
30
|
-
'_ansible_no_log', # jic we
|
30
|
+
'_ansible_no_log', # jic we didn't clean up well enough, DON'T LOG
|
31
31
|
'_ansible_verbose_override', # controls display of ansible_facts, gathering would be very noise with -v otherwise
|
32
32
|
)
|
33
33
|
|
ansible/galaxy/api.py
CHANGED
@@ -92,7 +92,7 @@ def g_connect(versions):
|
|
92
92
|
try:
|
93
93
|
data = self._call_galaxy(n_url, method='GET', error_context_msg=error_context_msg, cache=True)
|
94
94
|
except (AnsibleError, GalaxyError, ValueError, KeyError) as err:
|
95
|
-
# Either the URL
|
95
|
+
# Either the URL doesn't exist, or other error. Or the URL exists, but isn't a galaxy API
|
96
96
|
# root (not JSON, no 'available_versions') so try appending '/api/'
|
97
97
|
if n_url.endswith('/api') or n_url.endswith('/api/'):
|
98
98
|
raise
|
@@ -877,7 +877,7 @@ class GalaxyAPI:
|
|
877
877
|
except GalaxyError as err:
|
878
878
|
if err.http_code != 404:
|
879
879
|
raise
|
880
|
-
# v3 doesn't raise a 404 so we need to
|
880
|
+
# v3 doesn't raise a 404 so we need to mimic the empty response from APIs that do.
|
881
881
|
return []
|
882
882
|
|
883
883
|
if 'data' in data:
|
@@ -449,9 +449,9 @@ def _extract_collection_from_git(repo_url, coll_ver, b_path):
|
|
449
449
|
except subprocess.CalledProcessError as proc_err:
|
450
450
|
raise AnsibleError( # should probably be LookupError
|
451
451
|
'Failed to switch a cloned Git repo `{repo_url!s}` '
|
452
|
-
'to the requested revision `{
|
452
|
+
'to the requested revision `{revision!s}`.'.
|
453
453
|
format(
|
454
|
-
|
454
|
+
revision=to_native(version),
|
455
455
|
repo_url=to_native(git_url),
|
456
456
|
),
|
457
457
|
) from proc_err
|
@@ -148,7 +148,7 @@ class CollectionDependencyProviderBase(AbstractProvider):
|
|
148
148
|
|
149
149
|
:param resolutions: Mapping of identifier, candidate pairs.
|
150
150
|
|
151
|
-
:param candidates: Possible candidates for the
|
151
|
+
:param candidates: Possible candidates for the identifier.
|
152
152
|
Mapping of identifier, list of candidate pairs.
|
153
153
|
|
154
154
|
:param information: Requirement information of each package.
|
@@ -158,7 +158,7 @@ class CollectionDependencyProviderBase(AbstractProvider):
|
|
158
158
|
:param backtrack_causes: Sequence of requirement information that were
|
159
159
|
the requirements that caused the resolver to most recently backtrack.
|
160
160
|
|
161
|
-
The preference could depend on
|
161
|
+
The preference could depend on various of issues, including
|
162
162
|
(not necessarily in this order):
|
163
163
|
|
164
164
|
* Is this package pinned in the current resolution result?
|
@@ -404,7 +404,7 @@ class CollectionDependencyProviderBase(AbstractProvider):
|
|
404
404
|
|
405
405
|
:param requirement: A requirement that produced the `candidate`.
|
406
406
|
|
407
|
-
:param candidate: A pinned candidate supposedly
|
407
|
+
:param candidate: A pinned candidate supposedly matching the \
|
408
408
|
`requirement` specifier. It is guaranteed to \
|
409
409
|
have been generated from the `requirement`.
|
410
410
|
|
ansible/inventory/group.py
CHANGED
@@ -26,7 +26,7 @@ from ansible import constants as C
|
|
26
26
|
from ansible.errors import AnsibleError
|
27
27
|
from ansible.module_utils.common.text.converters import to_native, to_text
|
28
28
|
from ansible.utils.display import Display
|
29
|
-
from ansible.utils.vars import combine_vars
|
29
|
+
from ansible.utils.vars import combine_vars, validate_variable_name
|
30
30
|
|
31
31
|
from . import helpers # this is left as a module import to facilitate easier unit test patching
|
32
32
|
|
@@ -221,6 +221,11 @@ class Group:
|
|
221
221
|
def set_variable(self, key: str, value: t.Any) -> None:
|
222
222
|
key = helpers.remove_trust(key)
|
223
223
|
|
224
|
+
try:
|
225
|
+
validate_variable_name(key)
|
226
|
+
except AnsibleError as ex:
|
227
|
+
Display().deprecated(msg=f'Accepting inventory variable with invalid name {key!r}.', version='2.23', help_text=ex._help_text, obj=ex.obj)
|
228
|
+
|
224
229
|
if key == 'ansible_group_priority':
|
225
230
|
self.set_priority(int(value))
|
226
231
|
else:
|
ansible/inventory/host.py
CHANGED
@@ -22,8 +22,10 @@ import typing as t
|
|
22
22
|
|
23
23
|
from collections.abc import Mapping, MutableMapping
|
24
24
|
|
25
|
+
from ansible.errors import AnsibleError
|
25
26
|
from ansible.inventory.group import Group, InventoryObjectType
|
26
27
|
from ansible.parsing.utils.addresses import patterns
|
28
|
+
from ansible.utils.display import Display
|
27
29
|
from ansible.utils.vars import combine_vars, get_unique_id, validate_variable_name
|
28
30
|
|
29
31
|
from . import helpers # this is left as a module import to facilitate easier unit test patching
|
@@ -117,7 +119,10 @@ class Host:
|
|
117
119
|
def set_variable(self, key: str, value: t.Any) -> None:
|
118
120
|
key = helpers.remove_trust(key)
|
119
121
|
|
120
|
-
|
122
|
+
try:
|
123
|
+
validate_variable_name(key)
|
124
|
+
except AnsibleError as ex:
|
125
|
+
Display().deprecated(msg=f'Accepting inventory variable with invalid name {key!r}.', version='2.23', help_text=ex._help_text, obj=ex.obj)
|
121
126
|
|
122
127
|
if key in self.vars and isinstance(self.vars[key], MutableMapping) and isinstance(value, Mapping):
|
123
128
|
self.vars = combine_vars(self.vars, {key: value})
|
@@ -38,11 +38,16 @@ def get_caller_plugin_info() -> _messages.PluginInfo | None:
|
|
38
38
|
_skip_stackwalk = True
|
39
39
|
|
40
40
|
if frame_info := _stack.caller_frame():
|
41
|
-
return
|
41
|
+
return _path_as_plugininfo(frame_info.filename)
|
42
42
|
|
43
43
|
return None # pragma: nocover
|
44
44
|
|
45
45
|
|
46
|
+
def _path_as_plugininfo(path: str) -> _messages.PluginInfo | None:
|
47
|
+
"""Return a `PluginInfo` instance if the provided `path` refers to a plugin."""
|
48
|
+
return _path_as_core_plugininfo(path) or _path_as_collection_plugininfo(path)
|
49
|
+
|
50
|
+
|
46
51
|
def _path_as_core_plugininfo(path: str) -> _messages.PluginInfo | None:
|
47
52
|
"""Return a `PluginInfo` instance if the provided `path` refers to a core plugin."""
|
48
53
|
try:
|
@@ -62,6 +67,10 @@ def _path_as_core_plugininfo(path: str) -> _messages.PluginInfo | None:
|
|
62
67
|
# Callers in this case need to identify the deprecating plugin name, otherwise only ansible-core will be reported.
|
63
68
|
# Reporting ansible-core is never wrong, it just may be missing an additional detail (plugin name) in the "on behalf of" case.
|
64
69
|
return ANSIBLE_CORE_DEPRECATOR
|
70
|
+
|
71
|
+
if plugin_name == '__init__':
|
72
|
+
# The plugin type is known, but the caller isn't a specific plugin -- instead, it's core plugin infrastructure (the base class).
|
73
|
+
return _messages.PluginInfo(resolved_name=namespace, type=plugin_type)
|
65
74
|
elif match := re.match(r'modules/(?P<module_name>\w+)', relpath):
|
66
75
|
# AnsiballZ Python package for core modules
|
67
76
|
plugin_name = match.group("module_name")
|
@@ -101,6 +110,8 @@ def _path_as_collection_plugininfo(path: str) -> _messages.PluginInfo | None:
|
|
101
110
|
|
102
111
|
name = '.'.join((match.group('ns'), match.group('coll'), match.group('plugin_name')))
|
103
112
|
|
113
|
+
# DTFIX-FUTURE: deprecations from __init__ will be incorrectly attributed to a plugin of that name
|
114
|
+
|
104
115
|
return _messages.PluginInfo(resolved_name=name, type=plugin_type)
|
105
116
|
|
106
117
|
|
ansible/module_utils/basic.py
CHANGED
@@ -1657,10 +1657,11 @@ class AnsibleModule(object):
|
|
1657
1657
|
ext = time.strftime("%Y-%m-%d@%H:%M:%S~", time.localtime(time.time()))
|
1658
1658
|
backupdest = '%s.%s.%s' % (fn, os.getpid(), ext)
|
1659
1659
|
|
1660
|
-
|
1661
|
-
|
1662
|
-
|
1663
|
-
|
1660
|
+
if not self.check_mode:
|
1661
|
+
try:
|
1662
|
+
self.preserved_copy(fn, backupdest)
|
1663
|
+
except (shutil.Error, IOError) as ex:
|
1664
|
+
raise Exception(f'Could not make backup of {fn!r} to {backupdest!r}.') from ex
|
1664
1665
|
|
1665
1666
|
return backupdest
|
1666
1667
|
|
@@ -1899,18 +1900,18 @@ class AnsibleModule(object):
|
|
1899
1900
|
the execution to hang (especially if no input data is specified)
|
1900
1901
|
:kw environ_update: dictionary to *update* environ variables with
|
1901
1902
|
:kw umask: Umask to be used when running the command. Default None
|
1902
|
-
:kw encoding: Since we return
|
1903
|
+
:kw encoding: Since we return strings, we need to
|
1903
1904
|
know the encoding to use to transform from bytes to text. If you
|
1904
1905
|
want to always get bytes back, use encoding=None. The default is
|
1905
1906
|
"utf-8". This does not affect transformation of strings given as
|
1906
1907
|
args.
|
1907
|
-
:kw errors: Since we return
|
1908
|
+
:kw errors: Since we return strings, we need to
|
1908
1909
|
transform stdout and stderr from bytes to text. If the bytes are
|
1909
1910
|
undecodable in the ``encoding`` specified, then use this error
|
1910
1911
|
handler to deal with them. The default is ``surrogate_or_strict``
|
1911
1912
|
which means that the bytes will be decoded using the
|
1912
1913
|
surrogateescape error handler if available (available on all
|
1913
|
-
|
1914
|
+
Python versions we support) otherwise a UnicodeError traceback
|
1914
1915
|
will be raised. This does not affect transformations of strings
|
1915
1916
|
given as args.
|
1916
1917
|
:kw expand_user_and_vars: When ``use_unsafe_shell=False`` this argument
|
@@ -1918,10 +1919,8 @@ class AnsibleModule(object):
|
|
1918
1919
|
are expanded before running the command. When ``True`` a string such as
|
1919
1920
|
``$SHELL`` will be expanded regardless of escaping. When ``False`` and
|
1920
1921
|
``use_unsafe_shell=False`` no path or variable expansion will be done.
|
1921
|
-
:kw pass_fds:
|
1922
|
-
|
1923
|
-
to an underlying ``Popen`` constructor. On Python 2, this will
|
1924
|
-
set ``close_fds`` to False.
|
1922
|
+
:kw pass_fds: This argument dictates which file descriptors should be passed
|
1923
|
+
to an underlying ``Popen`` constructor.
|
1925
1924
|
:kw before_communicate_callback: This function will be called
|
1926
1925
|
after ``Popen`` object will be created
|
1927
1926
|
but before communicating to the process.
|
@@ -1932,11 +1931,10 @@ class AnsibleModule(object):
|
|
1932
1931
|
:kw handle_exceptions: This flag indicates whether an exception will
|
1933
1932
|
be handled inline and issue a failed_json or if the caller should
|
1934
1933
|
handle it.
|
1935
|
-
:returns: A 3-tuple of return code (
|
1936
|
-
and stderr
|
1937
|
-
|
1938
|
-
|
1939
|
-
strings on python3, use encoding=None to turn decoding to text off.
|
1934
|
+
:returns: A 3-tuple of return code (int), stdout (str), and stderr (str).
|
1935
|
+
stdout and stderr are text strings converted according to the encoding
|
1936
|
+
and errors parameters. If you want byte strings, use encoding=None
|
1937
|
+
to turn decoding to text off.
|
1940
1938
|
"""
|
1941
1939
|
# used by clean args later on
|
1942
1940
|
self._clean = None
|