
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 }}" 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 (lab ecrire-code/boucles-loop)
Section intitulée « Cas pratique — créer des users avec filtrage (lab ecrire-code/boucles-loop) »Voici l’exemple validé sur le lab 20-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 |
| 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.
Pratiquer dans le lab
Section intitulée « Pratiquer dans le lab »Cette page a un lab d’accompagnement : labs/ecrire-code/boucles-loop/ dans
stephrobert/ansible-training. Il contient
un README.md guidé, un Makefile (make verify lance les tests), et un
challenge final auto-évalué : créer 3 users via loop: + when: + loop_control: label, dont 2 actifs et 1 désactivé.
Une fois le lab provisionné :
cd ~/Projets/ansible-training/labs/ecrire-code/boucles-loop/
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).