Aller au contenu
Infrastructure as Code medium

Boucles legacy with_* Ansible : pourquoi migrer vers loop

10 min de lecture

Logo Ansible

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

  • L'équivalence entre les with_* legacy et la forme moderne loop:
  • 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:)
Forme legacyForme moderne
with_items: ma_listeloop: ma_liste
with_list: ma_listeloop: ma_liste (alias)
with_dict: mon_dictloop: "{{ 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_listeloop: ma_liste + loop_control: index_var: idx
with_together: [list1, list2]loop: "{{ list1 | zip(list2) | list }}"
with_first_found: liste_pathsloop: "{{ query('first_found', paths) }}"
with_random_choice: ma_listeloop: "{{ [my_list | random] }}" (un seul choix)

Trois raisons concrètes :

  1. Lisibilité : loop: accepte directement des filtres Jinja2. Compare :

    # ❌ Legacy : trois directives différentes selon le type de source
    with_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') }}"
  2. Cohérence avec loop_control: : la forme moderne se combine naturellement avec loop_control: label:, index_var:, pause:, extended:.

  3. Convention RHCE 2026 : ansible-lint profil production signale les with_* comme anti-pattern (use-loop rule). Sur l'examen RHCE, l'examinateur attend la forme moderne.

# Avant
- name: Installer
ansible.builtin.dnf:
name: "{{ item }}"
with_items:
- nginx
- redis
# Après
- name: Installer
ansible.builtin.dnf:
name: "{{ item }}"
loop:
- nginx
- redis

Identique au mot près, juste with_itemsloop.

# 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: 6379

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

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

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

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

SymptômeCauseFix
with_dict migré en loop: my_dict (sans dict2items)Oubli du filtreloop: "{{ my_dict | dict2items }}"
loop: itère sur une string caractère par caractèreLa variable est une string, pas une listesplit() ou wantlist=true selon contexte
with_subelements migré incorrectementDifférence de structureTester avec debug: avant de remplacer
loop: plus lent que with_itemsIdem, le coût est le mêmeAucun fix nécessaire
ansible-lint signale use-loopCode legacyMigrer ; ansible-lint --fix peut aider
  • loop: remplace tous les with_* 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 --fix propose la migration automatique sur du code existant.
  • Aucune raison technique de garder les with_* en nouveau code en 2026.

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

Fenêtre de terminal
cd ~/Projets/ansible-training/labs/ecrire-code/boucles-with-deprecated/
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 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