Templates Jinja2
Objectifs du chapitre
- Comprendre la syntaxe Jinja2
- Créer des templates dynamiques
- Utiliser les filtres et les tests
- Gérer les structures complexes
1 - Introduction aux templates
Pourquoi les templates ?
Module template
tasks:
- name: Générer la configuration nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: yes
validate: nginx -t -c %s
2 - Syntaxe Jinja2
Trois types de balises
{# Ceci est un commentaire - ignoré #}
{{ variable }} {# Affiche une variable #}
{% if condition %} {# Structure de contrôle #}
{% endif %}
Variables simples
# nginx.conf.j2
server {
listen {{ http_port }};
server_name {{ server_name }};
root {{ document_root }};
}
Avec les variables :
http_port: 80
server_name: example.com
document_root: /var/www/html
Résultat :
server {
listen 80;
server_name example.com;
root /var/www/html;
}
Dictionnaires
# database.conf.j2
[database]
host = {{ database.host }}
port = {{ database.port }}
name = {{ database.name }}
user = {{ database.user }}
password = {{ database.password }}
database:
host: localhost
port: 5432
name: myapp
user: admin
password: secret
3 - Structures de contrôle
Conditions (if)
{% if ssl_enabled %}
server {
listen 443 ssl;
ssl_certificate {{ ssl_cert }};
ssl_certificate_key {{ ssl_key }};
}
{% else %}
server {
listen 80;
}
{% endif %}
Conditions avec elif
{% if environment == 'production' %}
log_level = warn
{% elif environment == 'staging' %}
log_level = info
{% else %}
log_level = debug
{% endif %}
Boucles (for)
# /etc/hosts
127.0.0.1 localhost
{% for host in web_servers %}
{{ host.ip }} {{ host.name }}
{% endfor %}
web_servers:
- { name: web-01, ip: "192.168.1.10" }
- { name: web-02, ip: "192.168.1.11" }
- { name: web-03, ip: "192.168.1.12" }
Résultat :
127.0.0.1 localhost
192.168.1.10 web-01
192.168.1.11 web-02
192.168.1.12 web-03
Boucles avec index
{% for server in upstream_servers %}
server {{ server }} weight={{ loop.index }};
{% endfor %}
| Variable | Description |
|---|---|
loop.index | Index (commence à 1) |
loop.index0 | Index (commence à 0) |
loop.first | True si premier élément |
loop.last | True si dernier élément |
loop.length | Nombre d'éléments |
{% for user in users %}
{{ user.name }}{% if not loop.last %}, {% endif %}
{% endfor %}
{# Résultat: alice, bob, charlie #}
4 - Filtres
Filtres de texte
{{ "hello" | upper }} {# HELLO #}
{{ "HELLO" | lower }} {# hello #}
{{ "hello world" | title }} {# Hello World #}
{{ "hello world" | capitalize }} {# Hello world #}
{{ " hello " | trim }} {# hello #}
{{ "hello" | replace("l", "x") }} {# hexxo #}
Filtres de valeur par défaut
{{ undefined_var | default("valeur_defaut") }}
{{ empty_var | default("fallback", true) }}
{{ my_var | default(omit) }} {# Omet la ligne si undefined #}
Filtres numériques
{{ 3.7 | round }} {# 4 #}
{{ 3.7 | round(1) }} {# 3.7 #}
{{ 3.7 | int }} {# 3 #}
{{ "42" | int }} {# 42 #}
{{ 1024 | human_readable }} {# 1 KB #}
Filtres de liste
{{ [1, 2, 3] | length }} {# 3 #}
{{ [1, 2, 3] | first }} {# 1 #}
{{ [1, 2, 3] | last }} {# 3 #}
{{ [3, 1, 2] | sort }} {# [1, 2, 3] #}
{{ [1, 2, 2, 3] | unique }} {# [1, 2, 3] #}
{{ [1, 2] + [3, 4] }} {# [1, 2, 3, 4] #}
{{ [1, 2, 3] | join(", ") }} {# 1, 2, 3 #}
{{ [1, 2, 3] | random }} {# élément aléatoire #}
Filtres JSON/YAML
{{ my_dict | to_json }}
{{ my_dict | to_nice_json }}
{{ my_dict | to_yaml }}
{{ my_dict | to_nice_yaml }}
Filtres de hachage
{{ "password" | hash('sha256') }}
{{ "password" | password_hash('sha512') }}
{{ "hello" | b64encode }}
{{ "aGVsbG8=" | b64decode }}
5 - Tests
Syntaxe des tests
{% if variable is defined %}
{% if variable is not defined %}
{% if variable is none %}
{% if number is even %}
{% if number is odd %}
{% if value is string %}
{% if value is number %}
{% if path is file %}
{% if path is directory %}
{% if name is match("^web-") %}
{% if name is search("pattern") %}
Exemples pratiques
{% if nginx_ssl_cert is defined and nginx_ssl_cert %}
ssl_certificate {{ nginx_ssl_cert }};
{% endif %}
{% if ansible_os_family is match("Debian|Ubuntu") %}
# Configuration Debian
{% endif %}
6 - Exemples avancés
Configuration nginx complète
# {{ ansible_managed }}
# Generated on {{ ansible_date_time.iso8601 }}
user {{ nginx_user | default('www-data') }};
worker_processes {{ nginx_worker_processes | default('auto') }};
pid /run/nginx.pid;
events {
worker_connections {{ nginx_worker_connections | default(1024) }};
{% if nginx_use_epoll | default(true) %}
use epoll;
{% endif %}
}
http {
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
access_log {{ nginx_access_log | default('/var/log/nginx/access.log') }};
error_log {{ nginx_error_log | default('/var/log/nginx/error.log') }};
# Gzip
{% if nginx_gzip_enabled | default(true) %}
gzip on;
gzip_types text/plain text/css application/json application/javascript;
{% endif %}
# Upstream
{% for upstream in nginx_upstreams | default([]) %}
upstream {{ upstream.name }} {
{% for server in upstream.servers %}
server {{ server.address }}:{{ server.port | default(80) }}{% if server.weight is defined %} weight={{ server.weight }}{% endif %};
{% endfor %}
}
{% endfor %}
# Virtual hosts
{% for vhost in nginx_vhosts | default([]) %}
server {
listen {{ vhost.port | default(80) }}{% if vhost.ssl | default(false) %} ssl{% endif %};
server_name {{ vhost.server_name | join(' ') if vhost.server_name is iterable and vhost.server_name is not string else vhost.server_name }};
root {{ vhost.root }};
{% if vhost.ssl | default(false) %}
ssl_certificate {{ vhost.ssl_certificate }};
ssl_certificate_key {{ vhost.ssl_certificate_key }};
{% endif %}
{% for location in vhost.locations | default([]) %}
location {{ location.path }} {
{% if location.proxy_pass is defined %}
proxy_pass {{ location.proxy_pass }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
{% else %}
try_files $uri $uri/ =404;
{% endif %}
}
{% endfor %}
}
{% endfor %}
}
Configuration PostgreSQL
# {{ ansible_managed }}
# PostgreSQL configuration
# Connection Settings
listen_addresses = '{{ postgresql_listen_addresses | default("localhost") }}'
port = {{ postgresql_port | default(5432) }}
max_connections = {{ postgresql_max_connections | default(100) }}
# Memory
shared_buffers = {{ (ansible_memtotal_mb * 0.25) | int }}MB
effective_cache_size = {{ (ansible_memtotal_mb * 0.75) | int }}MB
work_mem = {{ postgresql_work_mem | default('4MB') }}
maintenance_work_mem = {{ postgresql_maintenance_work_mem | default('64MB') }}
# WAL
wal_level = {{ postgresql_wal_level | default('replica') }}
max_wal_senders = {{ postgresql_max_wal_senders | default(3) }}
# Logging
log_destination = 'stderr'
logging_collector = on
log_directory = '{{ postgresql_log_directory | default("/var/log/postgresql") }}'
log_filename = 'postgresql-%Y-%m-%d.log'
log_rotation_age = 1d
log_min_duration_statement = {{ postgresql_log_min_duration | default(1000) }}
{% if postgresql_hba_entries is defined %}
# pg_hba.conf entries will be configured separately
{% endif %}
7 - Macros
Définir une macro
{% macro nginx_server(name, port=80, ssl=false) %}
server {
listen {{ port }}{% if ssl %} ssl{% endif %};
server_name {{ name }};
{% if ssl %}
ssl_certificate /etc/ssl/certs/{{ name }}.crt;
ssl_certificate_key /etc/ssl/private/{{ name }}.key;
{% endif %}
}
{% endmacro %}
Utiliser la macro
{{ nginx_server('example.com') }}
{{ nginx_server('secure.example.com', 443, true) }}
8 - Whitespace control
Problème
{% for item in items %}
{{ item }}
{% endfor %}
Génère des lignes vides.
Solution
{%- for item in items %}
{{ item }}
{%- endfor %}
{# Le - supprime les espaces/newlines #}
| Syntaxe | Effet |
|---|---|
{%- | Supprime whitespace avant |
-%} | Supprime whitespace après |
{{- | Supprime whitespace avant |
-}} | Supprime whitespace après |
9 - Bonnes pratiques
En-tête de fichier
# {{ ansible_managed }}
# Do not edit manually - changes will be overwritten
# Template: {{ template_path }}
# Generated: {{ ansible_date_time.iso8601 }}
# Host: {{ inventory_hostname }}
Validation
tasks:
- name: Generate nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: nginx -t -c %s
notify: Reload nginx
Backup
tasks:
- name: Generate config with backup
template:
src: app.conf.j2
dest: /etc/app/app.conf
backup: yes
10 - Structure de templates
Inclure d'autres templates
# nginx.conf.j2
http {
{% include 'includes/ssl.conf.j2' %}
{% include 'includes/security.conf.j2' %}
}
Résumé
Points clés
- Utilisez
{{ ansible_managed }}en en-tête - Exploitez les filtres pour transformer les données
- Testez toujours avec
validatequand possible - Gérez les whitespaces avec
{%-et-%}
Exercices pratiques
- Créez un template nginx avec vhosts dynamiques
- Générez un fichier /etc/hosts depuis l'inventaire
- Utilisez des conditions pour différents OS
- Créez une macro réutilisable