Ansible / Intermediate: reuse, secrets, and scoped validation / Lesson 7
Render templates from validated data contracts
Separate Jinja expressions, native values, controller-side rendering, template trust, and target configuration validation.
On this page
Lesson goals
- Explain controller-side templating and module boundaries.
- Use filters, tests, and defaults with explicit type intent.
- Validate rendered output without treating external data as template code.
Templating happens before target execution
Ansible evaluates Jinja-based expressions on the control node before the relevant task is sent to its execution destination. A template file is therefore not a remote script that discovers its own values. Lookups and filters can perform controller-side work or obtain external data, so template evaluation is not automatically inert. Identify which variables are required, which source owns them, and whether they are trusted as template content or ordinary data. A rendered configuration should be a deterministic function of approved inputs rather than a hidden combination of controller environment, timestamps, and implicit discovery.
Filters transform data; tests answer questions
A filter such as a deliberate conversion or collection transformation changes a value, while a test such as is defined checks a property. default can supply an optional fallback, but using it everywhere can conceal required missing input. Preserve list and dictionary types rather than joining them into text too early. Modern native templating can retain non-string values in expressions, while a configuration file ultimately needs the intended textual representation. Validate types before rendering, and keep application-specific escaping or serialization separate from shell quoting and YAML quoting. Each downstream format has its own grammar.
Data is not permission to evaluate another template
The templating overhaul introduced in ansible-core 2.19 changed trust and error behavior. The 2.21 baseline requires reviewing older patterns that relied on repeated implicit templating or truthy strings. Do not force arbitrary external content through another evaluation pass to make an error disappear. Keep template source under project review and treat values from inventory plugins, files, or APIs according to their intended data role. A string containing {{ ... }} should not automatically become executable template logic merely because it reached a variable. Read the porting guide and validate the actual supported engine path.
Template deployment needs an output-validation plan
The template module can compare rendered content with a destination and supports documented metadata and validation features. A validation command must be selected for the real application and its safe invocation contract; a parser success is not the same as service acceptance. Specify ownership, mode, destination, and secret-handling requirements deliberately. Never include volatile timestamps in a stable configuration unless the application genuinely requires them. Such inputs can produce changed on every run and repeatedly notify handlers. The following task is illustrative only and never writes its synthetic destination.
- name: Illustrate a synthetic configuration template
hosts: lab_nodes
gather_facts: false
tasks:
- name: Describe template deployment intent
ansible.builtin.template:
src: report.conf.j2
dest: /srv/synthetic-report/report.conf
mode: '0640'
The referenced template and destination are a fictional lab design. YAML parsing does not render Jinja, validate application syntax, or deploy a file; the block remains non-runnable.
Test deterministic output without overstating the renderer
A small offline formatter can check the expected content and newline contract for approved synthetic values. It is not a Jinja renderer and does not test Ansible’s trust or native-type behavior. In this environment Jinja2 and ansible-core are absent, so only YAML parsing and pure Python models are executed. A later authorized test should render the real template with the pinned engine, compare exact content, reject missing required data, and run the application’s supported validation on an isolated copy. Preserve both a valid case and malformed input instead of accepting any nonempty file as correct.
settings = {'mode': 'review', 'workers': 2}
assert type(settings['workers']) is int and settings['workers'] > 0
rendered = f"mode={settings['mode']}\nworkers={settings['workers']}\n"
print(rendered, end='')
mode=review
workers=2
This Python formatter produces the expected two lines only. It does not execute Jinja or deploy a template, so its success must not be reported as an Ansible template test.
Common errors and fixes
A default hides a required missing input
- Why it happens
- Optional fallback replaced schema validation.
- How to fix it
- Assert required values before rendering.
- Prevent it next time
- Document which defaults are genuinely optional.
A timestamp causes perpetual changes
- Why it happens
- Volatile controller data entered stable configuration.
- How to fix it
- Remove unjustified volatility and compare exact output.
- Prevent it next time
- Test repeated rendering with fixed inputs.
A Python formatter is called a Jinja engine test
- Why it happens
- A limited model was confused with actual renderer execution.
- How to fix it
- Report the tool and boundary actually exercised.
- Prevent it next time
- Keep parser, renderer, and application checks separate.
Check your understanding
1. Why can correct YAML still produce an invalid target configuration?
Show worked solution
YAML validates the automation document’s data syntax, while the rendered application format has another grammar and required semantics. Validate exact output with the application’s supported tool on an isolated copy and keep deployment effects separate.
2. How should external text containing template delimiters be treated by default?
Show worked solution
Treat it as data under an explicit schema rather than deliberately re-evaluating it as template code. Review any intended trusted-template interface separately, including its source, privileges, and pinned engine behavior.
Key takeaways
- Validate types, trust, and output grammar independently.
- Keep stable templates deterministic and tests honestly scoped.