ansible/ansible
Templating
Variable interpolation, conditional evaluation, and {{ ... }} rendering are powered by a hardened wrapper around upstream Jinja2. The new internal engine lives in lib/ansible/_internal/_templating/; the legacy public surface at lib/ansible/template/ still exists and is being phased out.
Files
| File | Role |
|---|---|
lib/ansible/_internal/_templating/_engine.py |
TemplateEngine and TemplateOptions — primary entry point |
lib/ansible/_internal/_templating/_jinja_bits.py |
AnsibleEnvironment, AnsibleTemplate, _TemplateCompileContext |
lib/ansible/_internal/_templating/_jinja_common.py |
Shared _TemplateConfig, marker errors, call-context support |
lib/ansible/_internal/_templating/_jinja_plugins.py |
Bridges Ansible filter/test/lookup plugins into Jinja |
lib/ansible/_internal/_templating/_jinja_patches.py |
Jinja2 monkey-patches required for the trust model |
lib/ansible/_internal/_templating/_marker_behaviors.py |
What to do when a name is undefined (FAIL_ON_UNDEFINED, etc.) |
lib/ansible/_internal/_templating/_lazy_containers.py |
Lazy-rendering wrappers for dicts/lists |
lib/ansible/_internal/_templating/_chain_templar.py |
Compatibility shim for legacy code calling Templar |
lib/ansible/_internal/_templating/_transform.py |
Post-render type transforms (e.g., bool, list, dict coercion) |
lib/ansible/_internal/_templating/_template_vars.py |
Variable scope helpers |
lib/ansible/_internal/_templating/_datatag.py |
DeprecatedAccessAuditContext and tag-related helpers |
lib/ansible/template/__init__.py |
Legacy Templar class (still public) |
lib/ansible/_internal/_datatag/_tags.py |
Origin, TrustedAsTemplate, VaultedValue tag definitions |
What's new in the trust model
Pre-2.18, every YAML scalar was a plain Python str. To template it, code called Templar.template(value), which looked for {{ / {% markers and ran them through Jinja. The trouble: a value coming back from a remote host (a fact, a registered task result, a piece of stdout) might contain literal {{ ... }} and accidentally be re-templated, with the variable scope of the controller. That's a server-side template injection.
The new model wraps every YAML scalar (and many other primitives) in an AnsibleTaggedObject carrying tags. Two tags matter for templating:
TrustedAsTemplate— set when the value originated from a YAML file the user authored (a playbook, a vars file, a role's defaults). Set by the YAML loader.Origin— set everywhere; tracks file + line + column.
TemplateEngine.template() checks for TrustedAsTemplate before running Jinja. Strings without it pass through unchanged, even if they contain {{.
The check happens at the level of "this string", not "this object graph". A list or dict can contain a mixture of trusted and untrusted strings; only the trusted ones are templated.
The marker class TemplateTrustCheckFailedError (in lib/ansible/errors/__init__.py) is raised when something tries to bypass the check incorrectly.
TemplateEngine
TemplateEngine (_engine.py) is the new primary entry point. Construct it with a DataLoader, a variable scope, and optional TemplateOptions. Call template(value) to render.
Key surface area (visible at the top of _engine.py):
template(value, options)— render a tagged value.template_object(graph, options)— recursively render a list/dict with mixed templates.transform_to_native_type(value)— bool/int/float/list/dict coercion when_native_typeis set.is_template(value)— predicate.
The engine wraps a jinja2.Environment (custom subclass AnsibleEnvironment) with:
- Ansible's filter, test, and lookup plugins bound (via
_jinja_plugins.py). - A
Origin-aware compile context that propagates source location through the template AST. - Custom marker behavior for
Undefined,Omit, and the_legacyJinja behavior switches.
How a value is rendered
graph TD
VALUE[Value with tags] --> CHECK{TrustedAsTemplate?}
CHECK -->|no| RETURN_AS_IS[Return value unchanged]
CHECK -->|yes| HAS_MARKER{Contains {{ or {%?}
HAS_MARKER -->|no| RETURN_TRIM[Return value as-is]
HAS_MARKER -->|yes| COMPILE[Compile via AnsibleEnvironment]
COMPILE --> EVAL[Render with current vars]
EVAL --> TRANSFORM{Native type requested?}
TRANSFORM -->|no| RETURN_STR[Return rendered string]
TRANSFORM -->|yes| COERCE[Coerce via _transform.py]
COERCE --> RETURN_TYPED[Return bool/int/list/dict]The transform step (_transform.py) handles the case where a when: expression should return a bool, or a vars: value should be a real int. There's a chain limit (TRANSFORM_CHAIN_LIMIT = 10 in _engine.py) that prevents pathological recursive transforms.
Filter, test, and lookup integration
Ansible's filter, test, and lookup plugins aren't ordinary Jinja extensions — they're discovered via the plugin loader and dynamically bound into the template environment.
- Filters:
{{ x | ansible.builtin.to_yaml }}callslib/ansible/plugins/filter/to_yaml.py._jinja_plugins.py:_invoke_filterwraps the plugin call so undefined-handling and tag propagation work. - Tests:
{{ x is defined }},{{ x is succeeded }}. Implemented inlib/ansible/plugins/test/. - Lookups:
{{ lookup('file', '/etc/hosts') }}and{{ q('file', '/etc/hosts') }}(the shorter aliasq). The lookup is invoked via_invoke_lookupin_jinja_plugins.py, which routes throughlookup_loader.
For collections, community.general.json_query etc. resolve through the same FQCN flow that the rest of the plugin loader uses.
Lazy containers
Templating an entire variable graph eagerly would be expensive — variables for one host can include thousands of facts. _lazy_containers.py defines lazy-rendering wrappers (_AnsibleLazyTemplateMixin) so a host_vars access doesn't render ansible_facts until something actually references it. The TQM hands these lazy containers off to workers.
Conditionals
Conditional (lib/ansible/playbook/conditional.py) wraps when: and failed_when: expressions. It strips the implicit {{ ... }} braces (these are bare expressions in YAML, not template strings) and runs them through TemplateEngine.template() with template_options.bare_jinja=True. The result is coerced to a bool, or AnsibleBrokenConditionalError is raised if it's not coercible.
This is why when: my_var works (truthy check), when: my_var == "expected" works (binary expression), and when: "{{ my_var }} == 'expected'" was deprecated long ago (you don't put template braces in when).
Origin propagation in errors
When a template error fires, the engine attempts to attach the underlying value's Origin to the exception. The result is errors like:
ERROR! the field 'name' has an invalid value (something), and could not be converted
to an str. The error was: ... encountered while templating ...
playbook.yml: line 5, column 7The Origin tag is what makes the file:line precision possible. See lib/ansible/_internal/_errors/_handler.py.
Markers and the omit token
Omit (lib/ansible/_internal/_templating/_utils.py) is a sentinel that means "this argument should be removed entirely". Used in module argument processing:
- name: maybe-set-mode
copy:
src: foo
dest: /tmp/foo
mode: '{{ desired_mode | default(omit) }}'If desired_mode is undefined, omit ends up in the rendered argument and the mode parameter is dropped before the module runs. Equivalent to "don't pass this arg".
Integration points
- Imports from:
ansible.parsing.dataloader,ansible.module_utils._internal._datatag,ansible._internal._datatag._tags,ansible.errors,ansible.utils.display,jinja2(vendored-style; specific subset). - Imported by: every place that templates anything — the playbook base class, the executor, lookup plugins, action plugins, conditionals.
- Patches Jinja2 through
_jinja_patches.pyto intercept undefined access and template compilation.
Entry points for modification
- Adding a filter or test — write a plugin in
lib/ansible/plugins/filter/orlib/ansible/plugins/test/(or in a collection). The auto-discovery picks it up. - Changing trust semantics — work in
_jinja_bits.py:_TemplateCompileContextandlib/ansible/_internal/_datatag/_tags.py. This is delicate; the trust model is part of the supported security boundary. - Adding a marker behavior — extend
_marker_behaviors.py:MarkerBehavior. - Tracking down a "template error" without a useful location — wire up additional
Origin.propagatecalls in the templating code, or adddisplay.vvvcalls in_jinja_plugins.py.
Cross-links
- Plugin loader — how filter/test/lookup plugins are discovered.
- Plugins → Filter, Plugins → Test, Plugins → Lookup.
- Vault encryption — the
VaultedValuetag that interacts with templating.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.