Aller au contenu
English
English
Infrastructure as Code medium

Boucles legacy with_* Ansible : pourquoi migrer vers loop

85 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_items → loop.

# 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/
mode: "0644"
with_fileglob: 'files/certs/*.pem'
# Après
- name: Distribuer
ansible.builtin.copy:
src: "{{ item }}"
dest: /etc/ssl/
mode: "0644"
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).

Voici l'exemple validé sur le lab 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

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

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

Migrer du code existant est plus formateur que lire une table de correspondance. Le lab vous fait repérer with_items, with_dict et with_subelements dans un playbook hérité, puis les réécrire en loop: avec les filtres Jinja2 correspondants. Vous terminez en laissant ansible-lint --fix faire la conversion mécanique, pour mesurer ce qu'il traite seul et ce qu'il vous laisse.

  • Templates Jinja2 : la vue d'ensemble de la syntaxe Jinja2 que la migration vers loop: mobilise.
  • Filtres Jinja2 avancés : le catalogue des filtres qui remplacent with_dict, with_subelements et leurs voisins.
  • Quiz Écrire du code : 30 questions pour vérifier que la forme moderne des boucles est acquise.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens ce site gratuitement, sans publicité, sans profilage et sans compte à créer. 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