Aller au contenu
Infrastructure as Code medium

Types collections Ansible : listes, dictionnaires, structures imbriquées

12 min de lecture

Logo Ansible

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.

  • Déclarer une liste de dicts en YAML (deux formats équivalents)
  • Accéder aux clés : var.key (notation pointée) et var['key'] (bracket)
  • Boucler sur une liste de dicts avec loop: et loop_control: label:
  • Filtrer une liste sur la valeur d’une clé avec selectattr('attr', 'equalto', 'value')
  • Extraire un champ avec map(attribute='name') + join
# 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.

# Format multi-ligne (recommandé)
nginx:
port: 80
workers: 4
enabled: true
# Format compact
nginx: { port: 80, workers: 4, enabled: true }

Idem : équivalents, mais le multi-ligne s’impose dès qu’une valeur est elle-même complexe.

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

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

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 ?

SituationPré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 absentevar.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).

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 tier

Le loop_control.label: rend la sortie console lisible — sans lui, vous voyez le dict complet répété. Le when: filtre les itérations.

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:8080
cache:6379

Les services web (staging) et dev-db (dev) sont exclus — c’est le rôle de selectattr.

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

SymptômeCauseFix
'dict object' has no attribute 'name'La clé n’existe pas dans certains itemsUtiliser `item.name
selectattr('tier', 'equalto', 'production') ne filtre rienequalto 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érationloop_control.label: manquantAjouter label: "{{ item.name }}"
var.items retourne la méthode PythonConflit avec items méthode dictUtiliser var['items'] (bracket) ou renommer la clé
Tableau imbriqué difficile à parcourir (e.g. services[].tags[])Boucle imbriquée requiseUtiliser with_subelements: ou aplatir avec flatten
  • 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.key et var['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.
  • dict2items transforme un dict en liste de {key, value} pour itérer sur les paires.

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

Fenêtre de terminal
cd ~/Projets/ansible-training/labs/ecrire-code/types-collections/
cat README.md # tuto pas à pas
cat challenge/README.md # consigne du challenge final
pytest -v challenge/tests/ # lancer les tests testinfra

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

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