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