Un state figé se répète. Jinja le rend générique : il s'exécute avant le YAML, injecte des variables, déroule des boucles et prend des décisions selon le minion. Ce module apprend à générer automatiquement une configuration Nginx qui s'adapte à chaque serveur. Public visé : intermédiaires à l'aise avec les states et les grains.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Utiliser variables, conditions et boucles dans un state.
- Appliquer des filtres Jinja courants.
- Factoriser du code avec des macros.
- Générer un fichier de configuration depuis des données.
Prérequis
Section intitulée « Prérequis »Jinja s'exécute avant le YAML
Section intitulée « Jinja s'exécute avant le YAML »Point clé souvent mal compris : Salt rend le Jinja d'abord, puis interprète
le YAML résultant. Les balises {% ... %} portent la logique (boucles,
conditions), les balises {{ ... }} insèrent une valeur. Le contexte
donne accès aux grains, aux pillars et aux fonctions Salt.
# le paquet dépend du système, via grain{% if grains['os_family'] == 'Debian' %}apache2: pkg.installed: []{% elif grains['os_family'] == 'RedHat' %}httpd: pkg.installed: []{% endif %}Boucles et données
Section intitulée « Boucles et données »Les boucles évitent de recopier vingt fois le même bloc. Combinées aux pillars, elles génèrent des states à partir de données : une liste de paquets, de vhosts ou d'utilisateurs décrite une fois, déroulée par Jinja.
# installer une liste de paquets issue d'un pillar{% for paquet in salt['pillar.get']('paquets_web', []) %}install-{{ paquet }}: pkg.installed: - name: {{ paquet }}{% endfor %}state.show_sls rend le state sans l'appliquer : c'est le moyen de vérifier
qu'une boucle produit bien les états attendus. À partir d'un pillar
paquets_web: [nginx, certbot], Jinja génère deux états pkg.installed :
salt web1 state.show_sls paquetsinstall-certbot: pkg: ...install-nginx: pkg: ...Filtres et macros
Section intitulée « Filtres et macros »Les filtres transforment une valeur avec la syntaxe valeur | filtre :
default pour une valeur de repli, upper, join, ou les filtres réseau et
yaml/json propres à Salt. Les macros factorisent un fragment réutilisable,
comme un bloc de configuration paramétré, appelé plusieurs fois avec des
arguments différents.
Pour appeler une fonction Salt depuis un template, utilisez la syntaxe
mapping avec des crochets : salt['pillar.get']('paquets_web') ou
salt['grains.get']('os_family'). C'est la forme recommandée par la
documentation officielle, plus robuste que la notation pointée
salt.pillar.get(...). Le deuxième argument fournit une valeur de repli :
salt['pillar.get']('port', 80) évite un rendu qui échoue sur une donnée
manquante.
{# macro de bloc serveur réutilisable #}{% macro vhost(nom, port) -%}server { listen {{ port }}; server_name {{ nom }}; root /var/www/{{ nom }};}{%- endmacro %}Jinja lit et met en forme, il n'agit pas
Section intitulée « Jinja lit et met en forme, il n'agit pas »Retenez une règle de sécurité que la documentation Salt martèle : le rendu
Jinja a lieu avant l'exécution du state, et il ignore test=True. Le
mode simulation n'arrête donc jamais une commande lancée depuis Jinja.
Concrètement, si un template appelle depuis Jinja une commande qui modifie le
système (créer un fichier, installer un paquet), l'action s'exécute même en
dry-run, et vous perdez à la fois le simulateur et l'idempotence.
La frontière est simple : Jinja lit et met en forme, il n'agit pas. Toute
opération qui change l'état appartient au state (pkg.installed,
file.managed, service.running), pas au template. Une lecture via
salt['pillar.get'](...) est sans effet de bord et donc légitime ; un
salt['cmd.run']('...') qui écrit quelque chose ne l'est pas.
Centraliser les spécificités OS dans un map.jinja
Section intitulée « Centraliser les spécificités OS dans un map.jinja »Quand un template doit gérer plusieurs distributions, ne dispersez pas les
noms de paquet et les chemins de configuration dans des {% if %}
répétés. La documentation recommande de rassembler ces valeurs spécifiques à
l'OS dans un fichier map.jinja réutilisable. La fonction grains.filter_by
choisit le bon jeu de valeurs selon la famille d'OS du minion :
salt web1 grains.filter_by \ '{"Debian": {"paquet": "nginx", "conf": "/etc/nginx/nginx.conf"}}' grain=os_familyweb1: ---------- conf: /etc/nginx/nginx.conf paquet: nginxUn state importe ensuite ces valeurs avec
{% from 'nginx/map.jinja' import nginx with context %}, puis référence
nginx.paquet et nginx.conf. La logique de plateforme vit à un seul
endroit, ce qui rend le state refactorable.
Cas pratique : générer une configuration Nginx
Section intitulée « Cas pratique : générer une configuration Nginx »Assemblons boucle, macro et pillar. Une liste de vhosts vit dans un pillar,
un template la déroule avec une macro par site, et file.managed le
déploie avec l'option template: jinja.
sites: - nom: blog.exemple.fr port: 80 - nom: api.exemple.fr port: 8080Le template combine la macro et la boucle sur le pillar :
{% macro vhost(nom, port) -%}server { listen {{ port }}; server_name {{ nom }}; root /var/www/{{ nom }};}{%- endmacro -%}{%- for site in salt['pillar.get']('sites', []) %}{{ vhost(site.nom, site.port) }}{% endfor -%}Le state le rend et l'écrit sur la cible :
/etc/nginx/conf.d/sites-generes.conf: file.managed: - source: salt://sites.conf.j2 - template: jinja - mode: "0644"Après application, le fichier généré contient un bloc par site, sans qu'on ait écrit deux fois le même modèle :
salt web1 state.apply vhostssalt web1 cmd.run 'cat /etc/nginx/conf.d/sites-generes.conf'server { listen 80; server_name blog.exemple.fr; root /var/www/blog.exemple.fr;}
server { listen 8080; server_name api.exemple.fr; root /var/www/api.exemple.fr;}Le second passage ne modifie rien : le rendu est déterministe, file.managed
idempotent.
Comment: File /etc/nginx/conf.d/sites-generes.conf is in the correct stateAjouter un site revient désormais à ajouter une entrée dans le pillar, pas à éditer la configuration : c'est tout l'intérêt de générer depuis des données.
À retenir
Section intitulée « À retenir »- Salt rend le Jinja avant le YAML :
{% %}pour la logique,{{ }}pour les valeurs. - Les boucles sur des pillars génèrent des states à partir de
données ;
state.show_slsvérifie le rendu sans appliquer. - Les filtres transforment les valeurs ;
defaultévite les états indéfinis. - Les macros factorisent les fragments répétés.
- Appelez Salt en syntaxe mapping :
salt['pillar.get']('foo:bar'), jamaissalt.pillar.get(...). - Jinja ne modifie jamais le système : il ignore
test=True, donc une commande d'écriture lancée depuis un template casse simulation et idempotence. - Centralisez les spécificités OS dans un
map.jinjaviagrains.filter_by.