
Au-delà des types simples (string, int, bool), les playbooks Ansible manipulent essentiellement deux structures complexes : les listes et les dictionnaires. Et le plus souvent, des listes de dictionnaires ou des dicts imbriqués — par exemple une liste de services avec chacun son nom, son port, ses tags. Cette page couvre la déclaration YAML de ces structures, les deux notations d’accès (var.key vs var['key']), et les boucles avec filtrage via selectattr: et map(attribute=...).
Maîtriser ces structures permet d’écrire des playbooks paramétrés : un seul play qui déploie 3 services en bouclant sur une liste de dicts, plutôt que 3 plays quasi-identiques.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Déclarer une liste de dicts en YAML (deux formats équivalents)
- Accéder aux clés :
var.key(notation pointée) etvar['key'](bracket) - Boucler sur une liste de dicts avec
loop:etloop_control: label: - Filtrer une liste sur la valeur d’une clé avec
selectattr('attr', 'equalto', 'value') - Extraire un champ avec
map(attribute='name')+join
Prérequis
Section intitulée « Prérequis »- Avoir lu Variables — déclaration et portées ;
- Connaître les bases YAML (cf. YAML pour Ansible).
Listes : deux formats YAML équivalents
Section intitulée « Listes : deux formats YAML équivalents »# Format multi-ligne avec tirets (lisible, recommandé)webservers: - web1.lab - web2.lab - web3.lab
# Format compact (flow style)webservers: [web1.lab, web2.lab, web3.lab]Les deux représentations sont strictement équivalentes. Le format multi-ligne est préféré pour la lisibilité dès que la liste dépasse 3-4 éléments ou contient des dicts.
Dictionnaires
Section intitulée « Dictionnaires »# Format multi-ligne (recommandé)nginx: port: 80 workers: 4 enabled: true
# Format compactnginx: { port: 80, workers: 4, enabled: true }Idem : équivalents, mais le multi-ligne s’impose dès qu’une valeur est elle-même complexe.
Structures imbriquées : listes de dicts
Section intitulée « Structures imbriquées : listes de dicts »C’est la structure la plus courante en Ansible. Une variable services qui décrit une liste de services, chacun étant un dict :
services: - name: api port: 8080 tier: production - name: web port: 80 tier: staging - name: cache port: 6379 tier: production - name: dev-db port: 5432 tier: devFormat compact équivalent :
services: - { name: api, port: 8080, tier: production } - { name: web, port: 80, tier: staging } - { name: cache, port: 6379, tier: production } - { name: dev-db, port: 5432, tier: dev }Ce format compact est utile pour les listes courtes où on veut voir tous les éléments rapidement. Au-delà de ~10 dicts, repassez en multi-ligne.
Accès aux clés : var.key vs var['key']
Section intitulée « Accès aux clés : var.key vs var['key'] »Deux syntaxes équivalentes pour accéder à une clé :
{{ nginx.port }} ← notation pointée (recommandée si lisible){{ nginx['port'] }} ← notation bracket (Python-style)Quand préférer l’une ou l’autre ?
| Situation | Préférer |
|---|---|
Clé simple, identifiant Python valide (port, name) | var.port (lisible) |
Clé contenant un caractère spécial (my-key, 1.0) | var['my-key'] |
Clé issue d’une variable ({{ vars[my_key_name] }}) | var[my_key_name] |
| Clé qui peut être absente | var.get('key', default) ou `var.key |
Piège : var.items est ambigu — items est aussi une méthode Python sur les dicts. Pour itérer sur les paires clé/valeur d’un dict, utilisez var.items() ou — plus propre en Ansible — le filtre dict2items (voir plus bas).
Boucler sur une liste
Section intitulée « Boucler sur une liste »Le mot-clé loop: itère sur une liste. Chaque itération expose la variable item :
- name: Poser un marqueur pour chaque service activé ansible.builtin.copy: dest: "/tmp/service-{{ item.name }}.txt" content: "service {{ item.name }} sur le port {{ item.port }}\n" mode: "0644" loop: "{{ services }}" loop_control: label: "{{ item.name }}" # affiche "api" plutôt que tout le dict when: item.tier == "production" # filtre par tierLe loop_control.label: rend la sortie console lisible — sans lui, vous voyez le dict complet répété. Le when: filtre les itérations.
Filtrer une liste avec selectattr
Section intitulée « Filtrer une liste avec selectattr »Quand vous voulez filtrer côté Jinja2 (sans when: au niveau task), le filtre selectattr est l’outil :
- name: Liste filtrée des services production ansible.builtin.debug: msg: "{{ services | selectattr('tier', 'equalto', 'production') | list }}"Sortie :
[ {"name": "api", "port": 8080, "tier": "production"}, {"name": "cache", "port": 6379, "tier": "production"}]Combiné avec map(attribute='name') pour extraire un champ :
{{ services | selectattr('tier', 'equalto', 'production') | map(attribute='name') | list }}Sortie : ['api', 'cache'].
Et avec join pour concaténer :
{{ services | map(attribute='name') | join(',') }}Sortie : 'api,web,cache,dev-db'.
Cas pratique — fichier filtré (lab ecrire-code/types-collections)
Section intitulée « Cas pratique — fichier filtré (lab ecrire-code/types-collections) »Voici l’exemple validé sur le lab 13-ecrire-code-types-collections qui combine tout :
---- name: Challenge types-collections selectattr hosts: db1.lab become: true
vars: services: - { name: api, port: 8080, tier: production } - { name: web, port: 80, tier: staging } - { name: cache, port: 6379, tier: production } - { name: dev-db, port: 5432, tier: dev }
tasks: - name: Poser un fichier filtré sur tier=production ansible.builtin.copy: dest: /tmp/services-production.txt content: | {% for s in services | selectattr('tier', 'equalto', 'production') %} {{ s.name }}:{{ s.port }} {% endfor %} mode: "0644"Le contenu posé sur db1.lab est :
api:8080cache:6379Les services web (staging) et dev-db (dev) sont exclus — c’est le rôle de selectattr.
Boucler sur un dict (clé/valeur) avec dict2items
Section intitulée « Boucler sur un dict (clé/valeur) avec dict2items »Pour itérer sur les paires clé/valeur d’un dict, le filtre dict2items transforme un dict en liste de {key, value} :
vars: ports_by_service: nginx: 80 redis: 6379 postgres: 5432
tasks: - name: Lister les ports ansible.builtin.debug: msg: "{{ item.key }} sur le port {{ item.value }}" loop: "{{ ports_by_service | dict2items }}"Sortie :
"msg": "nginx sur le port 80""msg": "redis sur le port 6379""msg": "postgres sur le port 5432"Le filtre inverse (items2dict) reconstruit un dict depuis une liste de {key, value}.
Pièges fréquents
Section intitulée « Pièges fréquents »| Symptôme | Cause | Fix |
|---|---|---|
'dict object' has no attribute 'name' | La clé n’existe pas dans certains items | Utiliser `item.name |
selectattr('tier', 'equalto', 'production') ne filtre rien | equalto est sensible au type (string vs int) | Vérifier que tier est bien une string en YAML, pas une enum |
| Loop affiche le dict complet à chaque itération | loop_control.label: manquant | Ajouter label: "{{ item.name }}" |
var.items retourne la méthode Python | Conflit avec items méthode dict | Utiliser var['items'] (bracket) ou renommer la clé |
Tableau imbriqué difficile à parcourir (e.g. services[].tags[]) | Boucle imbriquée requise | Utiliser with_subelements: ou aplatir avec flatten |
À retenir
Section intitulée « À retenir »- Le YAML accepte deux formats pour listes/dicts (multi-ligne et compact) — préférer le multi-ligne dès 3-4 éléments.
var.keyetvar['key']sont équivalents ; utiliser bracket pour les clés contenant des caractères spéciaux ou variables.loop:est la forme moderne (RHCE 2026) ;with_items:est legacy.loop_control.label:rend la sortie console lisible quand on boucle sur des dicts.selectattr('attr', 'equalto', 'value')filtre une liste de dicts ;map(attribute='name')extrait un champ.dict2itemstransforme un dict en liste de{key, value}pour itérer sur les paires.
Pratiquer dans le lab
Section intitulée « Pratiquer dans le lab »Cette page a un lab d’accompagnement : labs/ecrire-code/types-collections/ dans
stephrobert/ansible-training. Il contient
un README.md guidé, un Makefile (make verify lance les tests), et un
challenge final auto-évalué : filtrer une liste de dicts avec selectattr(‘tier’, ‘equalto’, ‘production’).
Une fois le lab provisionné :
cd ~/Projets/ansible-training/labs/ecrire-code/types-collections/
cat README.md # tuto pas à pascat challenge/README.md # consigne du challenge finalpytest -v challenge/tests/ # lancer les tests testinfraSi 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).