Chapter Six
Jinja2 Templating
How Ansible turns variables and facts into real config files. The two Jinja2 delimiters, the template module with validation, the filters and tests that do the daily work, and lookups — the control-node reads that show the agentless model surfacing in the templating layer. Whitespace control and the footguns that bite once a template stops being trivial.
Every config file in the Larkspur stack that varies between hosts or environments is a template, and Jinja2 is the engine that fills it. This chapter takes you from the two delimiters — {{ }} to print a value, {% %} to run logic — through the template module that renders larkspur.conf and app.env onto each web server, to the filters and tests that turn inventory data into config lines. By the end a single .j2 serves web1, web2, staging, and prod from one source of truth.
The spine of the chapter is where rendering happens. Templates render on the control node, against the host's variables and facts, and only the finished file ships out — the managed node never sees Jinja2. That is invisible until you reach lookups, where lookup('file', '/etc/hostname') reads your laptop, not the server, and the agentless push model surfaces in plain sight. The last topic cleans up the whitespace and the footguns that turn a working template into a noisy or broken one.
Topics in This Chapter
{{ }} prints a value, {% %} runs control flow and prints nothing. Variables and facts inside templates, where templating happens, and why rendering runs on the control node..j2 into a real config file with src, dest, and permissions. Why template beats copy, what validate= and %s protect, and how a changed result drives handlers.default, default(omit), and mandatory for optional and required values; to_json for valid serialized config; and map/select chaining to build config lines from inventory in one expression.is — is defined, is in, is number. Combined with {% if %} and {% for %} to render a TLS block conditionally and an upstream list per web host.file, env, password, first_found. Why a lookup reads your laptop, not the server, and query() vs lookup() when you loop.{%- -%} and trim_blocks/lstrip_blocks to kill the blank lines a loop leaves. Undefined-variable behavior, recursive templating, and the footguns that bite a non-trivial template.