Aller au contenu
Infrastructure as Code medium

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

70 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) }}"
mode: "0644"

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

Voici l'exemple validé sur le lab 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.

Ces filtres s'enchaînent, et c'est la chaîne complète qui résiste au premier essai. Le lab vous fait couvrir une variable absente avec default, filtrer une liste de dicts avec selectattr et map, puis fusionner deux dictionnaires avec combine. Le résultat attendu est une chaîne déterministe, ce qui oblige à terminer par unique | sort | join plutôt qu'à compter sur l'ordre d'origine.

  • Conditions avec when : c'est dans les conditions que ces filtres servent le plus souvent au quotidien.
  • Filtres Jinja2 avancés : regex_search, password_hash et to_nice_yaml prennent le relais quand les dix essentiels ne suffisent plus.
  • Tests Jinja2 : un test répond par vrai ou faux là où un filtre transforme, et la confusion entre les deux est fréquente.

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn