mirror of https://github.com/ansible/ansible.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
79 lines
3.2 KiB
Python
79 lines
3.2 KiB
Python
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
import typing as t
|
|
|
|
from collections import abc as c
|
|
|
|
from ansible import constants
|
|
from ansible._internal._templating import _engine
|
|
from ansible._internal._templating._chain_templar import ChainTemplar
|
|
from ansible.errors import AnsibleError
|
|
from ansible.module_utils._internal._ambient_context import AmbientContextBase
|
|
from ansible.module_utils.datatag import native_type_name
|
|
from ansible.parsing import vault as _vault
|
|
from ansible.utils.display import Display
|
|
|
|
if t.TYPE_CHECKING:
|
|
from ansible.playbook.task import Task
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class TaskContext(AmbientContextBase):
|
|
"""Ambient context that wraps task execution on workers. It provides access to the currently executing task."""
|
|
|
|
task: Task
|
|
|
|
|
|
TaskArgsFinalizerCallback = t.Callable[[str, t.Any, _engine.TemplateEngine, t.Any], t.Any]
|
|
"""Type alias for the shape of the `ActionBase.finalize_task_arg` method."""
|
|
|
|
|
|
class TaskArgsChainTemplar(ChainTemplar):
|
|
"""
|
|
A ChainTemplar that carries a user-provided context object, optionally provided by `ActionBase.get_finalize_task_args_context`.
|
|
TaskArgsFinalizer provides the context to each `ActionBase.finalize_task_arg` call to allow for more complex/stateful customization.
|
|
"""
|
|
|
|
def __init__(self, *sources: c.Mapping, templar: _engine.TemplateEngine, callback: TaskArgsFinalizerCallback, context: t.Any) -> None:
|
|
super().__init__(*sources, templar=templar)
|
|
|
|
self.callback = callback
|
|
self.context = context
|
|
|
|
def template(self, key: t.Any, value: t.Any) -> t.Any:
|
|
return self.callback(key, value, self.templar, self.context)
|
|
|
|
|
|
class TaskArgsFinalizer:
|
|
"""Invoked during task args finalization; allows actions to override default arg processing (e.g., templating)."""
|
|
|
|
def __init__(self, *args: c.Mapping[str, t.Any] | str | None, templar: _engine.TemplateEngine) -> None:
|
|
self._args_layers = [arg for arg in args if arg is not None]
|
|
self._templar = templar
|
|
|
|
def finalize(self, callback: TaskArgsFinalizerCallback, context: t.Any) -> dict[str, t.Any]:
|
|
resolved_layers: list[c.Mapping[str, t.Any]] = []
|
|
|
|
for layer in self._args_layers:
|
|
if isinstance(layer, (str, _vault.EncryptedString)): # EncryptedString can hide a template
|
|
if constants.config.get_config_value('INJECT_FACTS_AS_VARS'):
|
|
Display().warning(
|
|
"Using a template for task args is unsafe in some situations "
|
|
"(see https://docs.ansible.com/ansible/devel/reference_appendices/faq.html#argsplat-unsafe).",
|
|
obj=layer,
|
|
)
|
|
|
|
resolved_layer = self._templar.resolve_to_container(layer, options=_engine.TemplateOptions(value_for_omit={}))
|
|
else:
|
|
resolved_layer = layer
|
|
|
|
if not isinstance(resolved_layer, dict):
|
|
raise AnsibleError(f'Task args must resolve to a {native_type_name(dict)!r} not {native_type_name(resolved_layer)!r}.', obj=layer)
|
|
|
|
resolved_layers.append(resolved_layer)
|
|
|
|
ct = TaskArgsChainTemplar(*reversed(resolved_layers), templar=self._templar, callback=callback, context=context)
|
|
|
|
return ct.as_dict()
|