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