
loop: est la directive moderne Ansible (2.5+) pour itérer une tâche sur une liste, un dict ou un résultat de lookup. Elle remplace les anciens with_items:, with_dict:, with_subelements: (page suivante : Boucles legacy with_*). Cette page couvre la syntaxe loop:, l'option loop_control: (label, index_var, pause), la combinaison avec when:, et les patterns courants : itérer sur une liste de dicts, sur un dict, ou sur le résultat d'un lookup('fileglob', ...).
C'est la directive la plus utilisée pour paramétrer un playbook : un seul module qui crée 10 users en bouclant sur une liste, plutôt que 10 tâches dupliquées.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- La syntaxe
loop: <liste>et la variableitem loop_control::label,index_var,pause,extended- Itérer sur une liste de dicts :
loop:+item.<key> - Itérer sur un dict via
dict2items - Combiner
loop:+when:: filtrage par item register:sur une boucle : récupérer la liste des résultats
Prérequis
Section intitulée « Prérequis »- Avoir lu Conditions, when ;
- Connaître les listes et dicts YAML (cf. Types collections).
La syntaxe loop:
Section intitulée « La syntaxe loop: »- name: Installer plusieurs paquets ansible.builtin.dnf: name: "{{ item }}" state: present loop: - nginx - redis - postgresqlÀ chaque itération, item vaut un élément de la liste. La tâche est exécutée 3 fois.
Itérer sur une liste de dicts
Section intitulée « Itérer sur une liste de dicts »Le pattern le plus courant en Ansible. Chaque item est un dict, on accède aux clés via item.<key> :
vars: users: - { name: alice, shell: /bin/bash, enabled: true } - { name: bob, shell: /bin/zsh, enabled: false } - { name: charlie, shell: /bin/bash, enabled: true }
tasks: - name: Créer les users ansible.builtin.user: name: "{{ item.name }}" shell: "{{ item.shell }}" state: present loop: "{{ users }}" loop_control: label: "{{ item.name }}" when: item.enabledSortie console :
TASK [Créer les users]skipping: [db1.lab] => (item=bob)changed: [db1.lab] => (item=alice)changed: [db1.lab] => (item=charlie)Sans loop_control.label:, Ansible afficherait le dict complet à chaque itération, illisible.
loop_control:, options de boucle
Section intitulée « loop_control:, options de boucle »| Option | Effet |
|---|---|
label: "<expr>" | Affichage console (au lieu du dict complet) |
index_var: idx | Variable contenant l'index courant (0-based) |
pause: <secondes> | Pause entre chaque itération (utile pour API rate-limited) |
extended: true | Active ansible_loop.first, ansible_loop.last, ansible_loop.index |
- name: Démo loop_control complet ansible.builtin.debug: msg: "Item {{ idx }} sur {{ users | length }} : {{ item.name }} (last={{ ansible_loop.last }})" loop: "{{ users }}" loop_control: label: "{{ item.name }}" index_var: idx pause: 1 extended: trueItérer sur un dict via dict2items
Section intitulée « Itérer sur un dict via dict2items »vars: ports: nginx: 80 redis: 6379 postgresql: 5432
tasks: - name: Lister les ports ansible.builtin.debug: msg: "{{ item.key }} : {{ item.value }}" loop: "{{ ports | dict2items }}" loop_control: label: "{{ item.key }}"dict2items transforme {a: 1, b: 2} en [{key: a, value: 1}, {key: b, value: 2}]. À l'inverse, items2dict reconstruit un dict.
Itérer sur un lookup
Section intitulée « Itérer sur un lookup »- name: Distribuer tous les certificats locaux ansible.builtin.copy: src: "{{ item }}" dest: "/etc/ssl/{{ item | basename }}" mode: "0644" loop: "{{ query('fileglob', 'files/certs/*.pem') }}"query('fileglob', ...) retourne une liste de chemins matchant le glob côté control node. Préfère query à lookup quand on itère (query retourne toujours une liste).
loop: + when: ensemble
Section intitulée « loop: + when: ensemble »when: est évalué à chaque itération :
- name: Installer uniquement les services activés ansible.builtin.dnf: name: "{{ item.name }}" state: present loop: "{{ services }}" when: item.enabledPour les itérations où when: est faux, la tâche est marquée skipped (pas failed).
register: sur une boucle
Section intitulée « register: sur une boucle »Quand vous capturez la sortie d'une tâche dans une boucle, le résultat est une liste :
- name: Vérifier des URLs en boucle ansible.builtin.uri: url: "https://{{ item }}/health" status_code: 200 loop: - api.example.com - app.example.com register: health_results
- name: Afficher tous les status ansible.builtin.debug: msg: "{{ item.item }} → {{ item.status }}" loop: "{{ health_results.results }}"Le résultat est dans health_results.results (liste), chaque entrée contient l'item original + les champs de la tâche (status, content, etc.).
Cas pratique, créer des users avec filtrage
Section intitulée « Cas pratique, créer des users avec filtrage »Voici l'exemple validé sur le lab ecrire-code-boucles-loop :
---- name: Challenge boucles-loop hosts: db1.lab become: true
vars: challenge_users: - { name: chal_alice, shell: /bin/bash, enabled: true } - { name: chal_bob, shell: /bin/zsh, enabled: false } - { name: chal_charlie, shell: /bin/bash, enabled: true }
tasks: - name: Créer les users actifs ansible.builtin.user: name: "{{ item.name }}" shell: "{{ item.shell }}" state: present loop: "{{ challenge_users }}" loop_control: label: "{{ item.name }}" when: item.enabled
- name: Poser le récapitulatif ansible.builtin.copy: dest: /tmp/loop-result.txt content: "{{ challenge_users | selectattr('enabled') | map(attribute='name') | sort | join(',') }}\n" mode: "0644"alice et charlie créés, bob skippé. Le récapitulatif loop-result.txt est généré côté Jinja sans loop (via selectattr | map | sort | join).
Pièges fréquents
Section intitulée « Pièges fréquents »| Symptôme | Cause | Fix |
|---|---|---|
| Loop affiche le dict complet à chaque itération | Pas de loop_control: label: | Toujours ajouter label: "{{ item.name }}" |
'dict object' has no attribute 'foo' | La clé n'existe pas dans certains items | item.foo | default('valeur') ou when: 'foo' in item |
| Loop trop lent | Module qui supporte une liste mais on boucle quand même | Passer la liste directement au module (name: ['a', 'b']) |
register avec loop renvoie un dict pas une liste | C'est une liste sous register_var.results | Utiliser register_var.results, pas register_var directement |
loop: ne propage pas les handlers | C'est par design (un handler est dédupliqué) | Pas de fix |
loop: avec un pause: ralentit le play | pause: est volontaire (rate limit API) | Réduire la valeur ou retirer le pause: |
À retenir
Section intitulée « À retenir »loop: <liste>+item= boucle moderne (Ansible 2.5+).with_*est legacy.loop_control: label: "{{ item.name }}"rend la sortie console lisible.- Pour itérer sur un dict, utiliser
{{ ports | dict2items }}(key/value). register:sur une boucle stocke la liste dansvar.results.- Si le module supporte une liste (
dnf,apt), passer la liste directement plutôt que boucler, plus rapide. when:est évalué à chaque itération : utile pour filtrer par item.
Mettre en pratique
Section intitulée « Mettre en pratique »Une boucle se juge sur sa sortie console autant que sur son résultat. Le lab vous fait itérer sur une liste simple puis sur une liste de dicts, rendre l'affichage lisible avec loop_control: label, et parcourir un dictionnaire via dict2items. La validation compte les comptes réellement créés sur la machine, ce qui met en évidence le filtrage par when: appliqué à chaque itération.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Block / rescue / always : encadrer une boucle pour qu'un item en échec déclenche une compensation au lieu d'arrêter le play.
- failed_when et changed_when : ajuster le statut de chaque itération quand la boucle porte sur des commandes.
- Filtres Jinja2 avancés : construire la liste sur laquelle vous itérez à partir de données brutes.