Templates Jinja2
Enregistrement vidéo du cours: Lien vers la vidéo
Jinja2 est un moteur de templates pour Python utilisé par Ansible pour générer des fichiers dynamiques. Il permet de créer des fichiers de configuration personnalisés en fonction de l'environnement, de l'inventaire ou d'autres variables.
Syntaxe de base :
{{ variable }}: Affiche une variable{% instruction %}: Structure de contrôle (if, for, etc.){# commentaire #}: Commentaire (non affiché dans le résultat)
Manipulation 2: Syntaxe de base et variables
Créer la structure du projet:
mkdir -p ansible-templates/{templates,generated}
cd ansible-templates
Créer un fichier d'inventaire hosts.ini:
[webservers]
web01 ansible_host=ADRESSE_IP
[all:vars]
ansible_user=ansible
ansible_password=Ansible123!
ansible_become_pass=Ansible123!
Créez un fichier de configuration ansible ansible.cfg:
[defaults]
inventory = hosts.ini
host_key_checking = False
Créer un template Jinja2 templates/simple.txt.j2:
{# Ceci est un commentaire - il ne sera pas affiché #}
===========================================
Configuration générée par Ansible
===========================================
Nom du serveur: {{ ansible_hostname }}
Adresse IP: {{ ansible_default_ipv4.address }}
Système d'exploitation: {{ ansible_distribution }} {{ ansible_distribution_version }}
Architecture: {{ ansible_architecture }}
Date de génération: {{ ansible_date_time.date }} à {{ ansible_date_time.time }}
{# Variables personnalisées de l'inventaire #}
Nom de domaine: {{ server_name }}
Nombre maximum de clients: {{ max_clients }}
À remarquer:
- Les variables comme
ansible_hostname,ansible_default_ipv4.address, etc., sont des faits Ansible collectés automatiquement. - Les variables
server_nameetmax_clientsdoivent être définies dans l'inventaire ou dans le playbook.
Créer un playbook Ansible generate_config.yml:
---
- name: Test des templates Jinja2 simples
hosts: webservers
gather_facts: yes
vars:
server_name: "mon-site-exemple.com"
max_clients: 150
tasks:
- name: Générer le fichier depuis le template
template:
src: templates/simple.txt.j2
dest: "/tmp/{{ ansible_hostname }}_config.txt"
mode: '0644'
- name: Afficher le contenu généré
slurp:
src: "/tmp/{{ ansible_hostname }}_config.txt"
register: generated_file
- name: Montrer le résultat
debug:
msg: "{{ generated_file.content | b64decode }}"
Explications:
- La première tâche utilise le module
templatepour générer un fichier de configuration à partir du template Jinja2. C'est le module clé pour travailler avec des templates.- Le paramètre
srcspécifie le chemin du template source du projet, qui contient des "placeholders", soit des endroits où les variables seront insérées. - Le paramètre
destspécifie le chemin de destination où le fichier généré sera sauvegardé sur le serveur cible. Lors du déploiement, les variables seront remplacées par leurs valeurs réelles. Le nom du fichier inclut le nom d'hôte à l'aide de la variableansible_hostname. Une fois déployé, l'extension.j2est supprimée. - Le paramètre
modedéfinit les permissions du fichier généré.
- Le paramètre
- La deuxième tâche utilise le module
slurppour lire le contenu du fichier généré, et le stocke dans la variablegenerated_file. - La troisième tâche utilise le module
debugpour afficher le contenu du fichier généré. La fonctionb64decodeest utilisée pour décoder le contenu encodé en base64 retourné par le moduleslurp.
Exécutez le playbook:
ansible-playbook generate_config.yml
Après l'exécution, vous devriez voir le contenu du fichier généré affiché dans la sortie du playbook, avec les variables remplacées par leurs valeurs réelles.
Pour vérifier directement sur le serveur géré, vous pouvez vous connecter en SSH et afficher le fichier généré (modifiez ADRESSE_IP avec l'adresse IP réelle de votre serveur et node1 avec le nom d'hôte réel):
ssh ansible@ADRESSE_IP 'cat /tmp/node1_config.txt'
Filtres Jinja2
Les filtres Jinja2 permettent de manipuler les variables dans les templates. Voici ls syntaxe de base pour utiliser un filtre:
{{ variable | filtre }}
Quelques filtres courants:
lower: Convertit une chaîne en minuscules.upper: Convertit une chaîne en majuscules.default(value): Fournit une valeur par défaut si la variable est indéfinie ou vide.replace("old", "new"): Remplace les occurrences de "old" par "new".length: Retourne la longueur d'une liste ou d'une chaîne.
Créez un template avec des filtres templates/filters.txt.j2:
===========================================
Démonstration des filtres Jinja2
===========================================
1. FILTRES DE CHAÎNES
---------------------
Nom du serveur (original): {{ ansible_hostname }}
Nom en majuscules: {{ ansible_hostname | upper }}
Nom en minuscules: {{ ansible_hostname | lower }}
Nom capitalisé: {{ ansible_hostname | capitalize }}
Nom avec titre: {{ ansible_hostname | title }}
2. FILTRES DE VALEURS PAR DÉFAUT
---------------------------------
Variable définie: {{ server_name | default('aucun nom') }}
Variable non définie: {{ variable_inexistante | default('valeur par défaut') }}
3. FILTRES DE REMPLACEMENT
---------------------------
Texte original: {{ ansible_hostname }}
Remplacement: {{ ansible_hostname | replace('node', 'serveur-') }} # Remplace 'node' par 'serveur-'
4. FILTRES DE LISTES
--------------------
{% set ma_liste = ['apache', 'nginx', 'mysql', 'redis'] %}
Liste originale: {{ ma_liste }}
Liste en texte avec virgules: {{ ma_liste | join(', ') }}
Liste avec trait d'union: {{ ma_liste | join(' - ') }}
Premier élément: {{ ma_liste | first }}
Dernier élément: {{ ma_liste | last }}
Nombre d'éléments: {{ ma_liste | length }}
Liste triée: {{ ma_liste | sort }}
5. FILTRES MATHÉMATIQUES
-------------------------
{% set nombre = 42 %}
Nombre: {{ nombre }}
Absolu de -42: {{ -42 | abs }}
Minimum de [10, 5, 20]: {{ [10, 5, 20] | min }}
Maximum de [10, 5, 20]: {{ [10, 5, 20] | max }}
Somme de [1, 2, 3, 4, 5]: {{ [1, 2, 3, 4, 5] | sum }}
6. FILTRES DE FORMAT
--------------------
Adresse IP: {{ ansible_default_ipv4.address }}
JSON: {{ ansible_default_ipv4 | to_json }}
YAML: {{ ansible_default_ipv4 | to_yaml }}
JSON indenté:
{{ ansible_default_ipv4 | to_nice_json }}
7. FILTRES DE DATES
-------------------
Date actuelle: {{ ansible_date_time.date }}
Timestamp: {{ ansible_date_time.epoch }}
8. FILTRES BOOLÉENS ET TESTS
-----------------------------
La variable est définie: {{ server_name is defined }}
La variable est une chaîne: {{ server_name is string }}
Le nombre est pair: {{ max_clients is even }}
Le nombre est impair: {{ max_clients is odd }}
Créez un playbook Ansible generate_filters.yml:
# Fichier: test_filters.yml
---
- name: Test des filtres Jinja2
hosts: web01
gather_facts: yes
vars:
server_name: "mon-site-exemple.com"
max_clients: 150
tasks:
- name: Générer le fichier de démonstration des filtres
template:
src: templates/filters_demo.j2
dest: /tmp/filters_demo.txt
mode: '0644'
- name: Récupérer et afficher le fichier
slurp:
src: /tmp/filters_demo.txt
register: result
- name: Afficher le résultat
debug:
msg: "{{ result.content | b64decode }}"
Exécutez le playbook:
ansible-playbook generate_filters.yml
Après l'exécution, vous devriez voir le contenu du fichier généré affiché dans la sortie du playbook, avec les variables transformées par les filtres Jinja2.
ssh ansible@ADRESSE_IP 'cat /tmp/filters_demo.txt'
Conditions et boucles
Jinja2 permet d'utiliser des structures de contrôle comme les conditions et les boucles pour rendre les templates plus dynamiques.
Créez un template avec conditions templates/conditions.txt.j2:
===========================================
Configuration pour {{ ansible_hostname }}
===========================================
{# Condition simple #}
{% if ansible_distribution == "Ubuntu" %}
# Configuration pour Ubuntu
apt update
apt install nginx
{% elif ansible_distribution == "CentOS" %}
# Configuration pour CentOS
yum install nginx
{% elif ansible_distribution == "Debian" %}
# Configuration pour Debian
apt-get update
apt-get install nginx
{% else %}
# Distribution non supportée
echo "Distribution {{ ansible_distribution }} non reconnue"
{% endif %}
{# Condition avec test de variable définie #}
{% if server_name is defined %}
Nom du serveur: {{ server_name }}
{% else %}
Nom du serveur: Non défini
{% endif %}
{# Conditions multiples #}
{% if max_clients is defined and max_clients > 100 %}
# Serveur haute capacité
MaxClients {{ max_clients }}
KeepAlive On
KeepAliveTimeout 5
{% else %}
# Serveur capacité standard
MaxClients 50
KeepAlive Off
{% endif %}
{# Condition avec opérateurs logiques #}
{% if ansible_memtotal_mb >= 4096 %}
memory = high
{% elif ansible_memtotal_mb >= 2048 %}
memory = medium
{% else %}
memory = low
{% endif %}
{# Test de contenu dans une liste #}
{% set supported_os = ['Ubuntu', 'Debian', 'CentOS'] %}
{% if ansible_distribution in supported_os %}
OS supporté: {{ ansible_distribution }}
{% else %}
ATTENTION: OS non supporté!
{% endif %}
{# Tests booléens #}
{% if max_clients is even %}
Le nombre de clients ({{ max_clients }}) est pair.
{% endif %}
{% if server_name is string %}
server_name est bien une chaîne de caractères.
{% endif %}
{# Condition inline (ternaire) #}
Mode: {{ 'production' if max_clients > 100 else 'développement' }}
Créez un playbook Ansible generate_conditions.yml:
---
- name: Test des conditions Jinja2
hosts: webservers
gather_facts: yes
tasks:
- name: Générer la configuration avec conditions
template:
src: templates/conditional.j2
dest: "/tmp/{{ ansible_hostname }}_conditional.txt"
- name: Afficher le résultat
slurp:
src: "/tmp/{{ ansible_hostname }}_conditional.txt"
register: result
- name: Montrer le contenu
debug:
msg: "{{ result.content | b64decode }}"
Exécutez le playbook:
ansible-playbook generate_conditions.yml
Boucles
Jinja2 permet de parcourir des listes ou des dictionnaires à l'aide de boucles for.
Créez un template avec boucles templates/loops.txt.j2:
===========================================
Démonstration des boucles Jinja2
===========================================
1. BOUCLE SIMPLE SUR UNE LISTE
-------------------------------
{% set packages = ['nginx', 'php-fpm', 'mysql-server', 'redis'] %}
Packages à installer:
{% for package in packages %}
- {{ package }}
{% endfor %}
2. BOUCLE AVEC INDEX
--------------------
{% for package in packages %}
{{ loop.index }}. {{ package }}
{% endfor %}
3. BOUCLE AVEC INDEX COMMENÇANT À 0
------------------------------------
{% for package in packages %}
[{{ loop.index0 }}] {{ package }}
{% endfor %}
4. VARIABLES SPÉCIALES DE BOUCLE
---------------------------------
{% for package in packages %}
Package: {{ package }}
- Index (à partir de 1): {{ loop.index }}
- Index (à partir de 0): {{ loop.index0 }}
- Premier élément: {{ loop.first }}
- Dernier élément: {{ loop.last }}
- Nombre total d'éléments: {{ loop.length }}
- Éléments restants: {{ loop.revindex }}
---
{% endfor %}
5. BOUCLE SUR UN DICTIONNAIRE
------------------------------
{% set services = {
'web': 'nginx',
'database': 'postgresql',
'cache': 'redis',
'queue': 'rabbitmq'
} %}
Services configurés:
{% for key, value in services.items() %}
{{ key }}: {{ value }}
{% endfor %}
Créez un playbook Ansible generate_loops.yml:
# Fichier: test_loops.yml
---
- name: Test des boucles Jinja2
hosts: web01
gather_facts: yes
tasks:
- name: Générer le fichier de démonstration des boucles
template:
src: templates/loops.j2
dest: /tmp/loops_demo.txt
- name: Afficher le résultat
slurp:
src: /tmp/loops_demo.txt
register: result
- name: Montrer le contenu
debug:
msg: "{{ result.content | b64decode }}"
Exemple réaliste: Configuration Nginx
Nginx est un serveur web populaire, similaire à Apache. Nginx utilise un fichier de configuration principal (nginx.conf) et des fichiers de configuration de site individuels (souvent situés dans /etc/nginx/sites-available/).
Nous allons utiliser Jinja2 pour générer ces fichiers de configuration.
Créez un template pour le fichier principal templates/nginx.conf.j2:
# (1) Utilisation d'un Filtre pour transformer la chaine
# Mettre le nom du site en majuscules pour le titre du commentaire
# Filtre: | upper
# =========================================
# Configuration pour le site {{ site_name | upper }}
# =========================================
server {
listen 80;
# (2) Utilisation d'une Condition: bloc de controle {% ... %}
{% if ssl_enabled %}
listen 443 ssl;
# Inclure le chemin vers les certificats uniquement si SSL est actif
ssl_certificate /etc/nginx/ssl/{{ site_name }}.crt;
ssl_certificate_key /etc/nginx/ssl/{{ site_name }}.key;
{% endif %}
server_name {{ site_name }};
# (3) Utilisation d'une Boucle: bloc de controle {% for ... %}
{% if users_list is defined %}
# Ajouter des entetes pour les utilisateurs specifiques
{% for user in users_list %}
location /~{{ user.username }} {
alias /var/www/users/{{ user.username }}/;
}
{% endfor %}
{% endif %}
# (4) Utilisation d'un autre Filtre
# Convertir la liste d'utilisateurs en une representation YAML propre
# Filtre: | to_nice_yaml
# Liste des utilisateurs :
# {{ users_list | to_nice_yaml }}
}
Remarquons:
- (1) Utilisation du filtre
upperpour mettre en majuscules le nom du site dans le commentaire. - (2) Utilisation d'une condition pour inclure les directives SSL uniquement si
ssl_enabledest vrai. - (3) Utilisation d'une boucle pour générer des blocs de configuration pour chaque utilisateur dans
users_list. - (4) Utilisation du filtre
to_nice_yamlpour afficher la liste des utilisateurs dans un format lisible.
Créez un playbook Ansible generate_nginx_config.yml:
---
- name: Generer une configuration NGINX avancée avec logique Jinja2
hosts: linux_servers
become: true
vars:
# Variable pour la condition (changer a false pour demontrer l'impact)
ssl_enabled: true
site_name: mon-projet-securise.com
# Variable pour la boucle
users_list:
- { username: "alice", uid: 1001 }
- { username: "bob", uid: 1002 }
tasks:
- name: Generer le fichier de configuration
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /tmp/nginx_config.conf
- name: Afficher le contenu genere pour verification
ansible.builtin.shell: cat /tmp/nginx_config.conf
register: advanced_config_output
- name: Resultat du Template Avance
debug:
var: advanced_config_output.stdout_lines
Explications:
- Le playbook définit des variables pour contrôler la génération du template, y compris une variable booléenne
ssl_enabledpour la condition et une listeusers_listpour la boucle. - La tâche utilise le module
templatepour générer le fichier de configuration Nginx en utilisant le template Jinja2. - Après la génération, le playbook affiche le contenu du fichier généré pour vérification. Exécutez le playbook:
ansible-playbook generate_nginx_config.yml
Différences entre template et copy
- Le module
templateutilise Jinja2 pour traiter les fichiers source, permettant l'insertion dynamique de variables, l'utilisation de conditions et de boucles. Il est idéal pour générer des fichiers de configuration personnalisés. - Le module
copycopie simplement un fichier statique d'une source à une destination sans traitement. Il est utilisé pour des fichiers qui ne nécessitent pas de personnalisation.
Cheat Sheet Jinja2 (feuille de référence)
Syntaxe de base Jinja2
{# Commentaire #}
{{ variable }} # Affichage
{% instruction %} # Structure de contrôle
{{ variable | filtre }} # Application de filtre
{{ variable | filtre(arg) }} # Filtre avec argument
Filtres les plus utilisés
{{ var | default('valeur') }} # Valeur par défaut
{{ var | upper }} # Majuscules
{{ var | lower }} # Minuscules
{{ var | capitalize }} # Première lettre en majuscule
{{ var | replace('old', 'new') }} # Remplacement
{{ liste | join(', ') }} # Jointure
{{ liste | length }} # Longueur
{{ liste | first }} # Premier élément
{{ liste | last }} # Dernier élément
{{ liste | sort }} # Tri
{{ dict | to_json }} # Conversion JSON
{{ dict | to_yaml }} # Conversion YAML
Structures de contrôle
{# Conditions #}
{% if condition %}...{% endif %}
{% if cond1 %}...{% elif cond2 %}...{% else %}...{% endif %}
{# Boucles #}
{% for item in liste %}...{% endfor %}
{% for key, value in dict.items %}...{% endfor %}
{# Variables de boucle #}
{{ loop.index }} # Index (commence à 1)
{{ loop.index0 }} # Index (commence à 0)
{{ loop.first }} # Premier élément?
{{ loop.last }} # Dernier élément?
{{ loop.length }} # Nombre total d'éléments
Whitespace control
{%- ... %} # Supprime les espaces AVANT
{% ... -%} # Supprime les espaces APRÈS
{%- ... -%} # Supprime les espaces AVANT et APRÈS
Tests courants
{% if var is defined %}
{% if var is undefined %}
{% if var is none %}
{% if var is string %}
{% if var is number %}
{% if num is even %}
{% if num is odd %}
{% if item in liste %}
Accès aux variables Ansible
{{ ansible_hostname }}
{{ ansible_default_ipv4.address }}
{{ ansible_distribution }}
{{ ansible_memtotal_mb }}
{{ groups['webservers'] }}
{{ hostvars[host]['ansible_host'] }}