Aller au contenu
Infrastructure as Code medium

Tests Jinja2 Ansible : is defined, is none, is mapping, is sequence

11 min de lecture

Logo Ansible

Les tests Jinja2 sont des prédicats booléens écrits avec is qui vérifient le type ou l’état d’une variable. Ils sont différents des filtres : un filtre transforme une valeur ({{ var | upper }}), un test retourne un booléen ({% if var is defined %}). Cette page liste les tests les plus utilisés en Ansible : is defined, is undefined, is none, is mapping, is sequence, is iterable, is string, is number, is regex.

Connaître ces tests permet d’écrire des templates et des conditions résilients aux variables manquantes ou de type inattendu.

  • La syntaxe var is <test> (avec is, pas ==)
  • Les tests d’existence : is defined, is undefined
  • Les tests de type : is mapping, is sequence, is string, is number
  • Les tests d’état : is none, is iterable, is regex
  • La différence filtre vs test (transformer vs vérifier)

Une confusion classique chez les débutants :

FormeTypeRetourExemple
&#123;&#123; var | default('x') &#125;&#125;filtrevaleur transformée&#123;&#123; undef | default('x') &#125;&#125;'x'
{% if var is defined %}testbooléen{% if undef is defined %}false

default est un filtre : il prend une valeur et en retourne une autre. defined est un test : il retourne true/false.

{% if user is defined %}
nom : {{ user.name }}
{% endif %}
{% if password is undefined %}
{# pas de password posé, on génère un random #}
{% set password = lookup('password', '/dev/null length=24') %}
{% endif %}

Cas typique : variable optionnelle qu’on ne veut interpoler que si elle existe.

{# Vérifier qu'on a bien un dict #}
{% if config is mapping %}
{% for key, value in config.items() %}
{{ key }} = {{ value }}
{% endfor %}
{% endif %}
{# Vérifier qu'on a bien une liste #}
{% if hosts is sequence and hosts is not string %}
{% for h in hosts %}
- {{ h }}
{% endfor %}
{% endif %}
{# Note : une string est aussi 'sequence' en Python — d'où le 'is not string' #}
TestType Python correspondant
is mappingdict, OrderedDict
is sequencelist, tuple, string (!)
is iterabletout itérable (incluant generators)
is stringstr
is numberint, float
is integerint
is floatfloat

Piège classique : is sequence matche aussi les strings (qui sont des séquences de caractères en Python). Combinez avec is not string si vous voulez uniquement liste/tuple.

{% if value is none %}
{# value vaut None / null #}
{% else %}
valeur : {{ value }}
{% endif %}
{# is regex : True si la string match une regex #}
{% if user.email is regex('^[a-z]+@[a-z]+\\.[a-z]+$') %}
email valide
{% endif %}

is regex est très utilisé pour valider des inputs avant de les utiliser.

{% if score is number and score >= 80 %}
note : excellent
{% elif score is divisibleby(2) %}
note : pair
{% endif %}
TestVrai si…
is divisibleby(N)var % N == 0
is evennombre pair
is oddnombre impair
{% if user is defined and user.email is defined and user.email is regex('^[^@]+@[^@]+$') %}
{# user existe, a un email, et l'email est valide #}
{% endif %}
{% if hosts is defined and hosts is sequence and hosts | length > 0 %}
{# hosts existe, est une liste, et n'est pas vide #}
{% endif %}

L’ordre des tests compte : user is defined doit venir avant user.email is defined, sinon l’accès à user.email plante quand user est absent.

Cas pratique — fichier conditionnel (lab ecrire-code/tests-jinja)

Section intitulée « Cas pratique — fichier conditionnel (lab ecrire-code/tests-jinja) »

Voici l’exemple validé sur le lab 28-ecrire-code-tests-jinja :

vars:
user: { name: alice, age: 30 }
config:
app: nginx
port: 80
ports: [80, 443, 8080]
# optional_var n'est PAS définie
tasks:
- name: Poser un fichier conditionnel
ansible.builtin.copy:
dest: /tmp/tests-jinja.txt
content: |
{% if user is defined %}
user_defined=yes
{% endif %}
{% if config is mapping %}
config_mapping=yes
{% endif %}
{% if ports is sequence %}
ports_sequence=yes
{% endif %}
{% if optional_var is undefined %}
optional_undefined=yes
{% endif %}

Sortie sur db1.lab :

user_defined=yes
config_mapping=yes
ports_sequence=yes
optional_undefined=yes

Les 4 tests sont vrais (chacun pour la bonne raison) et chaque ligne apparaît dans le fichier.

Les tests fonctionnent aussi dans when: au niveau task :

- name: Tâche conditionnelle sur un test
ansible.builtin.copy:
dest: /tmp/foo
content: "{{ user.name }}\n"
when:
- user is defined
- user.email is regex('^[^@]+@[^@]+$')

Lecture : “exécuter cette tâche si user existe ET son email match la regex”.

SymptômeCauseFix
'undefined' object has no attributeAccès à un attribut sans is defined parentTester chaque niveau : var is defined and var.foo is defined
is sequence retourne true sur une stringUne string est une séquence de caractères en PythonCombiner : is sequence and is not string
var == None ne marche pasMauvais opérateurUtiliser var is none (avec is, pas ==)
is regex plante avec une regex invalideErreur de syntaxe regexTester la regex avec python3 -c 'import re; re.compile("...")'
Confusion default (filtre) et defined (test)Les deux gèrent l’absence de variable, mais différemmentFiltre transforme, test retourne booléen — choisir selon le besoin
  • Tests Jinja2 = prédicats booléens écrits avec is (is defined, is mapping…).
  • Différents des filtres : filtre transforme une valeur, test retourne un booléen.
  • is defined + accès chaîné : tester chaque niveau pour éviter les NoneType has no attribute.
  • is sequence matche aussi les strings — ajouter is not string si vous voulez uniquement liste/tuple.
  • is regex valide une string contre une regex Python.
  • Les tests fonctionnent dans templates (.j2) ET dans when: au niveau task.

Cette page a un lab d’accompagnement : labs/ecrire-code/tests-jinja/ dans stephrobert/ansible-training. Le challenge combine 4 tests Jinja (is defined, is mapping, is sequence, is undefined) dans un fichier conditionnel.

Fenêtre de terminal
cd ~/Projets/ansible-training/labs/ecrire-code/tests-jinja/
cat README.md
pytest -v challenge/tests/

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