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.
Files changed (101) hide show
  1. ansible/_internal/_templating/_jinja_bits.py +16 -1
  2. ansible/_internal/_templating/_jinja_common.py +1 -1
  3. ansible/cli/__init__.py +2 -2
  4. ansible/cli/adhoc.py +6 -3
  5. ansible/cli/console.py +1 -1
  6. ansible/cli/doc.py +2 -2
  7. ansible/config/base.yml +9 -6
  8. ansible/executor/module_common.py +8 -5
  9. ansible/executor/powershell/psrp_put_file.ps1 +1 -1
  10. ansible/executor/task_executor.py +2 -2
  11. ansible/executor/task_queue_manager.py +34 -70
  12. ansible/executor/task_result.py +1 -1
  13. ansible/galaxy/api.py +2 -2
  14. ansible/galaxy/collection/concrete_artifact_manager.py +2 -2
  15. ansible/galaxy/dependency_resolution/providers.py +3 -3
  16. ansible/inventory/group.py +6 -1
  17. ansible/inventory/host.py +6 -1
  18. ansible/module_utils/_internal/_deprecator.py +12 -1
  19. ansible/module_utils/ansible_release.py +1 -1
  20. ansible/module_utils/basic.py +14 -16
  21. ansible/module_utils/common/yaml.py +1 -1
  22. ansible/module_utils/csharp/Ansible.Basic.cs +1 -1
  23. ansible/module_utils/csharp/Ansible.Privilege.cs +2 -2
  24. ansible/module_utils/facts/hardware/base.py +1 -1
  25. ansible/module_utils/facts/other/facter.py +1 -1
  26. ansible/module_utils/facts/system/distribution.py +2 -2
  27. ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 +1 -1
  28. ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1 +1 -1
  29. ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 +1 -1
  30. ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1 +1 -1
  31. ansible/module_utils/urls.py +1 -1
  32. ansible/modules/apt.py +9 -3
  33. ansible/modules/assemble.py +5 -3
  34. ansible/modules/expect.py +5 -5
  35. ansible/modules/hostname.py +2 -2
  36. ansible/modules/pip.py +9 -11
  37. ansible/modules/raw.py +2 -2
  38. ansible/modules/stat.py +1 -1
  39. ansible/modules/wait_for.py +10 -3
  40. ansible/parsing/mod_args.py +38 -20
  41. ansible/parsing/vault/__init__.py +3 -3
  42. ansible/playbook/base.py +0 -2
  43. ansible/playbook/helpers.py +1 -1
  44. ansible/playbook/playbook_include.py +23 -56
  45. ansible/playbook/role/__init__.py +38 -21
  46. ansible/plugins/action/__init__.py +2 -2
  47. ansible/plugins/action/assemble.py +2 -1
  48. ansible/plugins/action/assert.py +2 -2
  49. ansible/plugins/action/script.py +5 -4
  50. ansible/plugins/action/template.py +1 -1
  51. ansible/plugins/callback/__init__.py +77 -87
  52. ansible/plugins/callback/default.py +0 -3
  53. ansible/plugins/callback/junit.py +0 -6
  54. ansible/plugins/connection/ssh.py +1 -1
  55. ansible/plugins/filter/pow.yml +1 -1
  56. ansible/plugins/filter/root.yml +1 -1
  57. ansible/plugins/filter/strftime.yml +3 -3
  58. ansible/plugins/filter/to_uuid.yml +1 -1
  59. ansible/plugins/inventory/script.py +1 -1
  60. ansible/plugins/loader.py +5 -0
  61. ansible/plugins/lookup/password.py +4 -6
  62. ansible/release.py +1 -1
  63. ansible/utils/display.py +16 -26
  64. ansible/utils/path.py +1 -1
  65. ansible/utils/vars.py +4 -1
  66. ansible/vars/manager.py +6 -3
  67. ansible/vars/reserved.py +6 -4
  68. {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/METADATA +1 -1
  69. {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/RECORD +101 -99
  70. ansible_test/_internal/__init__.py +5 -0
  71. ansible_test/_internal/ansible_util.py +1 -1
  72. ansible_test/_internal/classification/python.py +6 -0
  73. ansible_test/_internal/cli/commands/__init__.py +0 -5
  74. ansible_test/_internal/cli/environments.py +51 -5
  75. ansible_test/_internal/commands/coverage/__init__.py +1 -1
  76. ansible_test/_internal/commands/integration/__init__.py +18 -5
  77. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  78. ansible_test/_internal/commands/sanity/__init__.py +3 -1
  79. ansible_test/_internal/commands/sanity/integration_aliases.py +11 -0
  80. ansible_test/_internal/commands/shell/__init__.py +43 -4
  81. ansible_test/_internal/commands/units/__init__.py +4 -1
  82. ansible_test/_internal/config.py +21 -13
  83. ansible_test/_internal/debugging.py +166 -0
  84. ansible_test/_internal/delegation.py +21 -13
  85. ansible_test/_internal/host_profiles.py +197 -6
  86. ansible_test/_internal/inventory.py +4 -0
  87. ansible_test/_internal/metadata.py +94 -4
  88. ansible_test/_internal/processes.py +80 -0
  89. ansible_test/_internal/python_requirements.py +27 -0
  90. ansible_test/_internal/target.py +8 -0
  91. ansible_test/_internal/util_common.py +13 -3
  92. ansible_test/_util/target/injector/python.py +8 -0
  93. {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/WHEEL +0 -0
  94. {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/entry_points.txt +0 -0
  95. {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/COPYING +0 -0
  96. {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  97. {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  98. {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  99. {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  100. {ansible_core-2.19.0b6.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  101. {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 pre-emptive raise on Marker args and lazy retrieval should be disabled for the macro invocation.
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 subsitutions or warnings."""
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 dont
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 cant/shouldnt work without a tty, so dont add prompt secrets
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 = {'action': {'module': context.CLIARGS['module_name'], 'args': module_args},
92
- 'timeout': context.CLIARGS['task_timeout']}
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
- stdout_callback=cb,
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
- stdout_callback=cb,
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 optoins
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 intepreter
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
- # DTFIX2: while module metadata works, this feature isn't fully baked and should be turned off before release
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 ModuleMetadataV1(
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
- # Satify pslint, we purposefully ignore this error as it is not critical it works.
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/unparseable result
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 unparseable results", async_result=async_result)
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
- stdout_callback: str | None = None,
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._stdout_callback: str | None | CallbackBase = stdout_callback
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._callbacks_loaded:
200
+ if self._callback_plugins:
203
201
  return
204
202
 
205
- stdout_callback_loaded = False
206
- if self._stdout_callback is None:
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
- if isinstance(self._stdout_callback, CallbackBase):
210
- stdout_callback_loaded = True
211
- elif isinstance(self._stdout_callback, string_types):
212
- if self._stdout_callback not in callback_loader:
213
- raise AnsibleError("Invalid callback for stdout specified: %s" % self._stdout_callback)
214
- else:
215
- self._stdout_callback = callback_loader.get(self._stdout_callback)
216
- self._stdout_callback.set_options()
217
- stdout_callback_loaded = True
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
- callback_list = list(callback_loader.all(class_only=True))
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
- if plugin not in callback_list:
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
- # for each callback in the list see if we should add it to 'active callbacks' used in the play
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
- if callback_name != self._stdout_callback or stdout_callback_loaded:
256
- display.vv("Skipping callback '%s', as we already have a stdout callback." % (callback_name))
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
- # skip initializing if we already did the work for the same plugin (even with diff names)
274
- if callback_obj not in self._callback_plugins:
275
- callback_obj.set_options()
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 e:
283
- display.warning("Skipping callback '%s', unable to load due to: %s" % (callback_name, to_native(e)))
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
- if not self._callbacks_loaded:
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
- if hasattr(callback_plugin, 'set_play_context'):
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 [self._stdout_callback] + self._callback_plugins:
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
- for possible in [method_name, 'v2_on_any']:
455
- method = getattr(callback_plugin, possible, None)
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
- methods.append(method)
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
@@ -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 didnt clean up well enough, DON'T LOG
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 doesnt exist, or other error. Or the URL exists, but isn't a galaxy API
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 mimick the empty response from APIs that do.
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 `{commitish!s}`.'.
452
+ 'to the requested revision `{revision!s}`.'.
453
453
  format(
454
- commitish=to_native(version),
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 identifer.
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 a various of issues, including
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 matchine the \
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
 
@@ -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
- validate_variable_name(key)
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 _path_as_core_plugininfo(frame_info.filename) or _path_as_collection_plugininfo(frame_info.filename)
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
 
@@ -17,6 +17,6 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = '2.19.0b6'
20
+ __version__ = '2.19.0b7'
21
21
  __author__ = 'Ansible, Inc.'
22
22
  __codename__ = "What Is and What Should Never Be"
@@ -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
- try:
1661
- self.preserved_copy(fn, backupdest)
1662
- except (shutil.Error, OSError) as ex:
1663
- raise Exception(f'Could not make backup of {fn!r} to {backupdest!r}.') from ex
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 native strings, on python3 we need to
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 native strings, on python3 we need to
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
- python3 versions we support) otherwise a UnicodeError traceback
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: When running on Python 3 this argument
1922
- dictates which file descriptors should be passed
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 (integer), stdout (native string),
1936
- and stderr (native string). On python2, stdout and stderr are both
1937
- byte strings. On python3, stdout and stderr are text strings converted
1938
- according to the encoding and errors parameters. If you want byte
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