Aller au contenu principal

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 %}
VariableDescription
loop.indexIndex (commence à 1)
loop.index0Index (commence à 0)
loop.firstTrue si premier élément
loop.lastTrue si dernier élément
loop.lengthNombre 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 #}
SyntaxeEffet
{%-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 validate quand possible
  • Gérez les whitespaces avec {%- et -%}

Exercices pratiques

  1. Créez un template nginx avec vhosts dynamiques
  2. Générez un fichier /etc/hosts depuis l'inventaire
  3. Utilisez des conditions pour différents OS
  4. Créez une macro réutilisable

← Roles | Ansible Galaxy →