
Avant Ansible 2.5, on utilisait with_items:, with_dict:, with_subelements: pour les boucles. Depuis 2.5, la forme moderne est loop: couplée à des filtres Jinja2 (dict2items, subelements, product). Cette page explique pourquoi loop: est préféré, comment migrer chaque variante legacy, et le piège de continuer à utiliser with_* dans un projet RHCE 2026 (perte de points).
Les with_* ne sont pas dépréciés au sens strict — ils continuent de fonctionner — mais la doc Ansible et les recommandations RHCE convergent toutes vers loop:.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- L’équivalence entre les
with_*legacy et la forme moderneloop: - La table de migration pour 6 cas courants
- Pourquoi
loop:est plus expressif (combinaison naturelle avec filtres) - L’impact RHCE 2026 (préférence à
loop:)
Prérequis
Section intitulée « Prérequis »- Avoir lu Boucles — loop ;
- Connaître les filtres Jinja2 essentiels (cf. Filtres Jinja2 essentiels).
La table de migration
Section intitulée « La table de migration »| Forme legacy | Forme moderne |
|---|---|
with_items: ma_liste | loop: ma_liste |
with_list: ma_liste | loop: ma_liste (alias) |
with_dict: mon_dict | loop: "{{ mon_dict | dict2items }}" |
with_fileglob: 'files/*.pem' | loop: "{{ query('fileglob', 'files/*.pem') }}" |
with_subelements: [users, ssh_keys] | loop: "{{ users | subelements('ssh_keys') }}" |
with_nested: [list1, list2] | loop: "{{ list1 | product(list2) | list }}" |
with_indexed_items: ma_liste | loop: ma_liste + loop_control: index_var: idx |
with_together: [list1, list2] | loop: "{{ list1 | zip(list2) | list }}" |
with_first_found: liste_paths | loop: "{{ query('first_found', paths) }}" |
with_random_choice: ma_liste | loop: "{{ [my_list | random] }}" (un seul choix) |
Pourquoi migrer
Section intitulée « Pourquoi migrer »Trois raisons concrètes :
-
Lisibilité :
loop:accepte directement des filtres Jinja2. Compare :# ❌ Legacy : trois directives différentes selon le type de sourcewith_items: "{{ users }}"with_dict: "{{ ports }}"with_fileglob: 'files/*.pem'# ✅ Moderne : une seule directive, filtre appropriéloop: "{{ users }}"loop: "{{ ports | dict2items }}"loop: "{{ query('fileglob', 'files/*.pem') }}" -
Cohérence avec
loop_control:: la forme moderne se combine naturellement avecloop_control: label:,index_var:,pause:,extended:. -
Convention RHCE 2026 :
ansible-lintprofil production signale leswith_*comme anti-pattern (use-looprule). Sur l’examen RHCE, l’examinateur attend la forme moderne.
Migration en pratique — with_items
Section intitulée « Migration en pratique — with_items »# Avant- name: Installer ansible.builtin.dnf: name: "{{ item }}" with_items: - nginx - redis
# Après- name: Installer ansible.builtin.dnf: name: "{{ item }}" loop: - nginx - redisIdentique au mot près, juste with_items → loop.
Migration en pratique — with_dict
Section intitulée « Migration en pratique — with_dict »# Avant- name: Lister ansible.builtin.debug: msg: "{{ item.key }}={{ item.value }}" with_dict: nginx: 80 redis: 6379
# Après- name: Lister ansible.builtin.debug: msg: "{{ item.key }}={{ item.value }}" loop: "{{ ports | dict2items }}" vars: ports: nginx: 80 redis: 6379Le filtre dict2items transforme {a:1, b:2} en [{key:a, value:1}, {key:b, value:2}] — chaque item est un dict avec .key et .value.
Migration en pratique — with_fileglob
Section intitulée « Migration en pratique — with_fileglob »# Avant- name: Distribuer ansible.builtin.copy: src: "{{ item }}" dest: /etc/ssl/ with_fileglob: 'files/certs/*.pem'
# Après- name: Distribuer ansible.builtin.copy: src: "{{ item }}" dest: /etc/ssl/ loop: "{{ query('fileglob', 'files/certs/*.pem') }}"query('fileglob', ...) retourne la liste de fichiers — équivalent direct.
Migration en pratique — with_subelements
Section intitulée « Migration en pratique — with_subelements »# Avant- name: Pour chaque user, déposer chacune de ses clés SSH ansible.posix.authorized_key: user: "{{ item.0.name }}" key: "{{ item.1 }}" with_subelements: - "{{ users }}" - ssh_keys
# Après- name: Pour chaque user, déposer chacune de ses clés SSH ansible.posix.authorized_key: user: "{{ item.0.name }}" key: "{{ item.1 }}" loop: "{{ users | subelements('ssh_keys') }}"Le filtre subelements('attr') sur une liste de dicts éclate la sous-clé en paires (parent, sub).
Cas pratique — migration validée (lab ecrire-code/boucles-with-deprecated)
Section intitulée « Cas pratique — migration validée (lab ecrire-code/boucles-with-deprecated) »Voici l’exemple validé sur le lab 21-ecrire-code-boucles-with-deprecated :
---- name: Challenge migration boucles legacy vers loop hosts: db1.lab become: true
vars: ports: nginx: 80 redis: 6379
tasks: - name: Iteration sur liste avec loop ansible.builtin.copy: dest: "/tmp/withitems-{{ item }}.txt" content: "{{ item }}\n" mode: "0644" loop: - apple - banana - cherry
- name: Iteration sur dict avec loop et dict2items ansible.builtin.copy: dest: "/tmp/withdict-{{ item.key }}.txt" content: "{{ item.value }}\n" mode: "0644" loop: "{{ ports | dict2items }}" loop_control: label: "{{ item.key }}"Aucun with_* — tout passe par loop: et dict2items. Les fichiers
/tmp/withitems-{apple,banana,cherry}.txt et /tmp/withdict-{nginx,redis}.txt
sont produits exactement comme avec les boucles legacy.
Que reste-t-il aux with_* ?
Section intitulée « Que reste-t-il aux with_* ? »Une seule justification fragile : lisibilité courte. with_items: ma_liste est un caractère plus court que loop: ma_liste. Mais ce gain est annulé dès qu’on ajoute loop_control:.
Recommandation : pas de nouveau code en with_* en 2026. Pour le code existant, migrer progressivement via ansible-lint --fix (qui propose la conversion automatique).
Pièges fréquents
Section intitulée « Pièges fréquents »| Symptôme | Cause | Fix |
|---|---|---|
with_dict migré en loop: my_dict (sans dict2items) | Oubli du filtre | loop: "{{ my_dict | dict2items }}" |
loop: itère sur une string caractère par caractère | La variable est une string, pas une liste | split() ou wantlist=true selon contexte |
with_subelements migré incorrectement | Différence de structure | Tester avec debug: avant de remplacer |
loop: plus lent que with_items | Idem, le coût est le même | Aucun fix nécessaire |
ansible-lint signale use-loop | Code legacy | Migrer ; ansible-lint --fix peut aider |
À retenir
Section intitulée « À retenir »loop:remplace tous leswith_*depuis Ansible 2.5 — convention RHCE 2026.- Pour un dict, ajouter le filtre
dict2items:loop: "{{ ports \| dict2items }}". - Pour un fileglob, utiliser
query('fileglob', ...):loop: "{{ query('fileglob', 'files/*.pem') }}". ansible-lint --fixpropose la migration automatique sur du code existant.- Aucune raison technique de garder les
with_*en nouveau code en 2026.
Pratiquer dans le lab
Section intitulée « Pratiquer dans le lab »Cette page a un lab d’accompagnement : labs/ecrire-code/boucles-with-deprecated/ dans
stephrobert/ansible-training. Il contient
un README.md guidé, un Makefile (make verify lance les tests), et un
challenge final auto-évalué : migrer 2 boucles legacy with_items / with_dict vers la forme moderne loop: + dict2items.
Une fois le lab provisionné :
cd ~/Projets/ansible-training/labs/ecrire-code/boucles-with-deprecated/
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).