Aller au contenu
Infrastructure as Code medium

Filtres Jinja2 essentiels Ansible : default, regex_replace, combine, selectattr

15 min de lecture

Logo Ansible

Les filtres Jinja2 transforment une valeur via la syntaxe {{ valeur | filtre }} — c’est le levier principal pour générer un fichier de config, fusionner deux sources, filtrer une liste ou nettoyer une string. Ansible expose les filtres standard Jinja2 + ses propres filtres custom (combine, selectattr étendu, dict2items…). Cette page liste les 10 filtres essentiels de la RHCE EX294 avec un exemple concret pour chacun.

C’est une page de référence : on revient la consulter quand on a besoin de transformer une variable et qu’on cherche le filtre adapté.

  • Le filtre default pour les variables optionnelles
  • regex_replace pour transformer une string par regex
  • to_json, to_yaml, to_nice_yaml pour la sérialisation
  • dict2items et items2dict pour boucler sur des dicts
  • combine pour fusionner deux dictionnaires
  • selectattr + map(attribute='name') pour filtrer une liste de dicts
  • unique, union, difference, intersect pour les opérations d’ensemble
  • length, min, max, sum pour les agrégats
  • upper, lower, title, trim pour les transformations de strings
  • Avoir lu les pages précédentes de la sous-section Variables et facts ;
  • Connaître la syntaxe Jinja2 de base ({{ }}, {% %}).
{{ undefined_var | default('fallback') }}

Si la variable est absente ou None, retourne 'fallback'. Plusieurs variantes :

{{ var | default('fallback', true) }} ← fallback aussi si var est falsy ('', 0, [], {})
{{ var | default(omit) }} ← omet le paramètre si var absente (Ansible spécifique)

omit est ultra-utile pour conditionner un argument de module :

- name: Créer un user (sans password si var absente)
ansible.builtin.user:
name: "{{ user_name }}"
password: "{{ user_password | default(omit) }}"
{{ 'foo-bar-baz' | regex_replace('-', '_') }}

Sortie : foo_bar_baz. Avec groupes de capture :

{{ 'web1.lab' | regex_replace('^(\w+)(\d+)\.(\w+)$', '\\1_\\2_\\3') }}

Sortie : web_1_lab (les \1, \2, \3 sont les groupes capturés).

Sérialisation d’une variable structurée :

{{ my_dict | to_json }} ← JSON compact
{{ my_dict | to_nice_json(indent=2) }} ← JSON indenté
{{ my_dict | to_yaml }} ← YAML
{{ my_dict | to_nice_yaml(indent=2) }} ← YAML indenté

Cas pratique : poser un fichier JSON à partir d’un dict Ansible :

- name: Poser un config JSON
ansible.builtin.copy:
dest: /etc/app/config.json
content: "{{ app_config | to_nice_json(indent=2) }}"

dict2items convertit un dict en liste de {key, value} (utile pour boucler) :

vars:
ports:
nginx: 80
redis: 6379
tasks:
- name: Itérer sur le dict
ansible.builtin.debug:
msg: "{{ item.key }} : {{ item.value }}"
loop: "{{ ports | dict2items }}"

items2dict fait l’inverse — utile quand vous avez une liste de paires que vous voulez transformer en dict :

{{ [{key: 'a', value: 1}, {key: 'b', value: 2}] | items2dict }}

Sortie : {'a': 1, 'b': 2}.

{{ {a: 1, b: 2} | combine({b: 99, c: 3}) }}

Sortie : {'a': 1, 'b': 99, 'c': 3}. Le second dict gagne sur le premier en cas de conflit.

Pour une fusion récursive (nested) :

{{ defaults | combine(overrides, recursive=true) }}

Pattern classique : merger des defaults avec un overrides de l’environnement.

{{ services | selectattr('env', 'equalto', 'prod') | list }}

Garde uniquement les éléments dont la clé env vaut 'prod'. Tests disponibles : equalto, defined, undefined, match (regex), search (regex), in, gt, lt.

{{ users | selectattr('age', 'gt', 18) | list }} ← users majeurs
{{ services | rejectattr('disabled') | list }} ← services non disabled
{{ services | map(attribute='name') | list }}

Sortie : liste des noms des services. Combiné avec selectattr :

{{ services | selectattr('env', 'equalto', 'prod') | map(attribute='name') | list }}

Liste des noms des services prod.

Opérations d’ensemble :

{{ ['a', 'b', 'a', 'c'] | unique }} ← ['a', 'b', 'c']
{{ ['a', 'b'] | union(['b', 'c']) }} ← ['a', 'b', 'c']
{{ ['a', 'b', 'c'] | difference(['b']) }} ← ['a', 'c']
{{ ['a', 'b'] | intersect(['b', 'c']) }} ← ['b']

Ne fonctionnent que sur des listes. Pour un dict, passez par keys() ou values().

Agrégats :

{{ groups['webservers'] | length }} ← nombre de webservers
{{ [10, 20, 5] | min }} ← 5
{{ [10, 20, 5] | max }} ← 20
{{ [10, 20, 5] | sum }} ← 35

Transformations de strings :

{{ 'hello' | upper }} ← HELLO
{{ 'HELLO' | lower }} ← hello
{{ 'hello world' | title }} ← Hello World
{{ ' hello ' | trim }} ← 'hello'
{{ 'foo-bar' | replace('-', '_') }} ← foo_bar

Cas pratique — combinaison de 6 filtres (lab ecrire-code/filtres-jinja-essentiels)

Section intitulée « Cas pratique — combinaison de 6 filtres (lab ecrire-code/filtres-jinja-essentiels) »

Voici l’exemple validé sur le lab 18-ecrire-code-filtres-jinja-essentiels :

vars:
raw_input: " HELLO World "
pkgs_a: ["nginx", "redis", "postgres"]
pkgs_b: ["redis", "memcached"]
services:
- { name: api, port: 8080, env: prod }
- { name: web, port: 80, env: staging }
- { name: cache, port: 6379, env: prod }
base_config: { app: api, port: 80 }
tls_overrides: { port: 443, tls: true }
tasks:
- name: Poser un fichier qui combine 6 filtres
ansible.builtin.copy:
dest: /tmp/filtres-result.txt
content: |
trimmed={{ raw_input | trim | lower }}
union={{ (pkgs_a + pkgs_b) | unique | sort | join(',') }}
prod_services={{ services | selectattr('env', 'equalto', 'prod') | map(attribute='name') | sort | join(',') }}
merged={{ (base_config | combine(tls_overrides)).items() | map('join', '=') | sort | join(' ') }}
default_value={{ undefined_var | default('fallback-OK') }}
yaml_safe={{ {'hello': 'world'} | to_yaml | trim }}
mode: "0644"

Contenu posé sur db1.lab :

trimmed=hello world
union=memcached,nginx,postgres,redis
prod_services=api,cache
merged=app=api port=443 tls=True
default_value=fallback-OK
yaml_safe="hello: world"

6 filtres en action : trim | lower, unique | sort | join, selectattr | map | sort | join, combine + items + map | sort | join, default, to_yaml | trim.

FiltreUsage
flatten(levels=N)Aplatit une liste de listes
randomÉlément aléatoire d’une liste
shuffleMélange une liste
dict_diff(other)Différence entre deux dicts
boolConvertit en booléen ('yes' | booltrue)
int, float, stringConversions de type
b64encode / b64decodeBase64
hash('sha256')Hashage
password_hash('sha512')Hash compatible /etc/shadow
regex_findall(pattern)Toutes les occurrences regex
community.general.json_query(...)JMESPath sur des structures complexes
SymptômeCauseFix
'list' object is not callableOubli des parenthèses sur lengthvar | length (pas var | length())
default retourne quand même la variableLa var existe mais est vide ('')Ajouter le true : var | default('x', true)
combine ne fusionne pas en profondeurMode non récursif par défautAjouter recursive=true
selectattr retourne toutMauvais test : utiliser 'equalto', pas '=='selectattr('env', 'equalto', 'prod')
to_yaml produit du !!set ou des balises bizarresVariable contient des objets non standardsPréférer to_nice_yaml ou caster en dict standard
  • default('x') = fallback ; default('x', true) = aussi pour valeur vide ; default(omit) = omet le paramètre.
  • regex_replace('pattern', '\\1') = transformation regex avec groupes de capture.
  • combine(dict, recursive=true) = fusion de dicts (récursive si demandé).
  • selectattr('attr', 'equalto', 'value') + map(attribute='name') = pattern central pour filtrer/extraire.
  • dict2items = transforme dict en liste de {key, value} pour boucler.
  • unique | sort | join(',') = chaîne classique pour produire une string déterministe.

Cette page a un lab d’accompagnement : labs/ecrire-code/filtres-jinja-essentiels/ dans stephrobert/ansible-training. Il contient un README.md guidé, un Makefile (make verify lance les tests), et un challenge final auto-évalué : appliquer 6 filtres dans un fichier marqueur : trim/lower, unique, selectattr, combine, default.

Une fois le lab provisionné :

Fenêtre de terminal
cd ~/Projets/ansible-training/labs/ecrire-code/filtres-jinja-essentiels/
cat README.md # tuto pas à pas
cat challenge/README.md # consigne du challenge final
pytest -v challenge/tests/ # lancer les tests testinfra

Si les tests passent, vous maîtrisez les concepts couverts dans ce guide. En cas de blocage, docs/troubleshooting.md à la racine du repo couvre les pièges fréquents (rate-limit SSH, clé absente, collection manquante).

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn