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 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