Tools/Ansible/Gestion des Boucles
Sommaire
Boucles et plug-in de recherche
L'utilisation de boucles pour itérer sur des tâches peut vous aider à simplifier vos playbooks Ansible. Le mot-clé loop effectue une boucle sur une liste d'éléments à plat. Lorsqu'il est utilisé en association avec des plug-ins de recherche, vous pouvez créer des données plus complexes dans vos listes pour vos boucles.
Le mot-clé loop
a été introduit dans Ansible 2.5. Avant cela, l'itération de la tâche a été mise en oeuvre à l'aide de mots-clés contenant en premier with_, suivis du nom d'un plug-in de recherche.
L'équivalent de loop dans cette syntaxe est with_list
et est conçu pour l'itération sur une liste plate simple.
Pour les listes simples, loop
est la meilleure syntaxe à utiliser.
À titre d'exemple, les trois syntaxes suivantes présentent les mêmes résultats. La première est celle qui est préférée :
- name: using loop
debug:
msg: "{{ item }}"
loop: "{{ mylist }}"
- name: using with_list
debug:
msg: "{{ item }}"
with_list: "{{ mylist }}"
- name: using lookup plugin
debug:
msg: "{{ item }}"
loop: "{{ lookup('list', mylist) }}"
Vous pouvez refactoriser une tâche d'itération de type with_*
pour utiliser le mot-clé loop
, en utilisant une combinaison appropriée de plug-ins de recherche et de filtres pour correspondre à la fonctionnalité.
L'utilisation du mot-clé loop
à la place de boucles de type with_*
présente les avantages suivants :
- Il n'est pas nécessaire de mémoriser ou de trouver un mot-clé de type
with_*
pour votre scénario d'itération. À la place, utilisez des plug-ins et des filtres pour adapter une tâche de mot-cléloop
à votre cas d'utilisation. - Concentrez-vous sur l'apprentissage des plug-ins et des filtres disponibles dans Ansible, dont la capacité d'application est plus étendue que la simple itération.
- Vous disposez d'un accès en ligne de commande à la documentation du plug-in de recherche, à l'aide de la commande
ansible-doc -t lookup
. Cela vous permet de découvrir les plug-ins de recherche et de concevoir des scénarios d'itération personnalisés à l'aide de ces derniers.
|
Scénarios d'itération
Les exemples suivants montrent comment créer des boucles plus complexes à l'aide d'expressions Jinja2, de filtres, de plug-ins de recherche et de la syntaxe with_*
.
Liste de listes
Le mot-clé with_items
permet d'effectuer une itération sur des listes complexes. Prenons l'exemple d'un playbook hypothétique avec la tâche suivante :
- name: Remove build files
file:
path: "{{ item }}"
state: absent
with_items:
- "{{ app_a_tmp_files }}"
- "{{ app_b_tmp_files }}"
- "{{ app_c_tmp_files }}"
La variable app_a_tmp_files
contient une liste de fichiers temporaires, tout comme app_b_tmp_files
et app_c_tmp_files
. Le mot-clé with_items
associe ces trois listes en une seule liste contenant les entrées des trois listes. Elle effectue automatiquement un aplatissement des niveaux de la liste.
Pour refactoriser une tâche with_items
afin d'utiliser le mot-clé loop
, utilisez le filtre flatten
.
Le filtre flatten
recherche de manière récursive les listes incorporées et crée une liste unique à partir des valeurs découvertes.
Le filtre flatten
accepte un argument levels qui spécifie un nombre entier de niveaux à rechercher pour les listes incorporées. Un argument {{{1}}}
spécifie que les valeurs sont obtenues uniquement par ordre décroissant dans une liste supplémentaire pour chaque élément de la liste initiale. Il s'agit du même aplatissement de niveau que celui obtenu implicitement par with_items
.
Pour refactoriser une tâche with_items
afin d'utiliser le mot-clé loop
, vous devez également utiliser le filtre {{{1}}}
:
- name: Remove build files
file:
path: "{{ item }}"
state: absent
loop: "{{ list_of_lists | flatten(levels=1) }}"
vars:
list_of_lists:
- "{{ app_a_tmp_files }}"
- "{{ app_b_tmp_files }}"
- "{{ app_c_tmp_files }}"
|
Listes imbriquées
Les données des fichiers de variables, des faits Ansible et des services externes sont souvent une composition de structures de données plus simples, telles que des listes et des dictionnaires.
Considérez la variable users
définie ci-dessous :
users:
- name: paul
password: "{{ paul_pass }}"
authorized:
- keys/paul_key1.pub
- keys/paul_key2.pub
mysql:
hosts:
- "%"
- "127.0.0.1"
- "::1"
- "localhost"
groups:
- wheel
- name: john
password: "{{ john_pass }}"
authorized:
- keys/john_key.pub
mysql:
password: other-mysql-password
hosts:
- "utility"
groups:
- wheel
- devops
La variable users est une liste. Chaque entrée de la liste est un dictionnaire avec les clés : name
,
password
, authorized
, mysql
et groups
. Les clés name
et password
définissent des chaînes simples, tandis que les clés authorized
et groups
définissent des listes. La clé mysql
fait référence à un autre dictionnaire qui contient des métadonnées liées à MySQL pour chaque utilisateur.
Tout comme le filtre flatten
, le filtre subelements
crée une liste unique à partir d'une liste contenant des listes imbriquées. Le filtre traite une liste de dictionnaires, et chaque dictionnaire contient une clé qui fait référence à une liste. Pour utiliser le filtre subelements
, vous devez indiquer le nom d'une clé sur chaque dictionnaire correspondant à une liste.
Pour illustrer cette opération, reprenons la définition de la variable users
précédente. Le filtre subelements
active l'itération par le biais de tous les utilisateurs et de leurs fichiers de clés
autorisés définis dans la variable :
- name: Set authorized ssh key
authorized_key:
user: "{{ item.0.name }}"
key: "{{ lookup('file', item.1) }}"
loop: "{{ users | subelements('authorized') }}"
Le filtre subelements
crée une liste à partir des données de la variable users. Chaque élément de la liste est lui-même une liste à deux éléments. Le premier élément contient une référence à chaque utilisateur. Le second élément contient une référence à une entrée unique à partir de la liste authorized
pour cet utilisateur.
Itération sur un dictionnaire
Vous rencontrez souvent des données organisées sous la forme d'un ensemble de paires clé/valeur, généralement dénommées dans la communauté Ansible en tant que dictionnaire, au lieu d'être structurée en tant que liste. À titre d'exemple, considérez la définition suivante d'une variable users
définie ci-dessous :
users:
demo1:
name: Demo User 1
mail: demo1@example.com
demo2:
name: Demo User 2
mail: demo2@example.com
...output omitted...
demo200:
name: Demo User 200
mail: demo200@example.com
Avant Ansible 2.5, vous devez utiliser le mot-clé with_dict
pour itérer au sein des paires clé/valeur de ce dictionnaire. Pour chaque itération, la variable item
dispose de deux attributs : key
et value
. L'attribut key
contient la valeur de l'une des clés de dictionnaire, tandis que l'attribut value contient les données associées à la clé du dictionnaire :
- name: Iterate over Users
user:
name: "{{ item.key }}"
comment: "{{ item.value.name }}"
state: present
with_dict: "{{ users }}"
Vous pouvez également utiliser le filtre dict2items
pour transformer un dictionnaire en une liste, ce qui est probablement plus facile à comprendre. Les éléments de cette liste sont structurés de la même façon que les éléments générés par le mot-clé with_dict
:
- name: Iterate over Users
user:
name: "{{ item.key }}"
comment: "{{ item.value.name }}"
state: present
loop: "{{ users | dict2items }}"
Itération sur un modèle d'extension (globbing) de fichier
Vous pouvez créer une boucle qui itère sur une liste de fichiers correspondant à un modèle d'extension de fichier fourni avec le plug-in de recherche fileglob
.
À titre d'exemple, considérez le play suivant :
- name: Test
hosts: localhost
gather_facts: no
tasks:
- name: Test fileglob lookup plugin
debug:
msg: "{{ lookup('fileglob', '~/.bash*') }}"
La sortie du plug-in de recherche fileglob
est une chaîne de fichiers séparés par des virgules, indiquée par l'utilisation du caractère de guillemets doubles (") autour des données de la variable msg
:
PLAY [Test] ******************************************************************
TASK [Test fileglob lookup plugin] *******************************************
ok: [localhost] => {
"msg": "/home/student/.bash_logout,/home/student/.bash_profile,/home/student/.bashrc,/home/student/.bash_history"
}
PLAY RECAP *******************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 ...
Pour forcer un plug-in de recherche à renvoyer une liste de valeurs, au lieu d'une chaîne de valeurs séparées par des virgules, utilisez le mot-clé query
à la place du mot-clé lookup
. Considérez la modification suivante de l'exemple de playbook précédent :
- name: Test
hosts: localhost
gather_facts: no
tasks:
- name: Test fileglob lookup plugin
debug:
msg: "{{ query('fileglob', '~/.bash*') }}"
La sortie de ce playbook modifié indique que le mot-clé msg
fait référence à une liste de fichiers, car les données figurent entre crochets ([...]
) :
PLAY [Test] ******************************************************************
TASK [Test fileglob lookup plugin] *******************************************
ok: [localhost] => {
"msg": [
"/home/student/.bash_logout",
"/home/student/.bash_profile",
"/home/student/.bashrc",
"/home/student/.bash_history"
]
}
PLAY RECAP *******************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 ...
Pour utiliser les données de ce plug-in de recherche dans une boucle, assurez-vous que les données traitées sont renvoyées sous la forme d'une liste. Les tâches du play suivant effectuent une itération sur les fichiers correspondant au modèle d'extension ~/.bash*
:
- name: Both tasks have the same result
hosts: localhost
gather_facts: no
tasks:
- name: Iteration Option One
debug:
msg: "{{ item }}"
loop: "{{ query('fileglob', '~/.bash*') }}"
- name: Iteration Option Two
debug:
msg: "{{ item }}"
with_fileglob:
- "~/.bash*"