Tools/Ansible/Gestion des Boucles

De MonPtitSite
Sauter à la navigation Sauter à la recherche
Accueil SysAdmin Hobbies                  
Ansible - Mise en oeuvre de boucles avancées

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.


IMPORTANT
Le message d'Ansible en amont a évolué depuis Ansible 2.5. Il est recommandé d'utiliser le mot-clé loop au lieu des boucles with_*, mais il existe des cas d'utilisation où l'ancienne syntaxe peut être plus adaptée. Conformément à la documentation, la syntaxe with_* n'est pas obsolète et doit être encore valide dans un avenir proche. La syntaxe de loop peut continuer à évoluer dans les versions à venir d'Ansible.

Voici quelques astuces clés, basées sur les conseils en amont d'Ansible 2.8 :

  • Le mot-clé loop requiert une liste et n'accepte pas de chaîne. Si cela vous pose problème, souvenez-vous de la différence entre lookup et query.
  • Toute utilisation de with_* qui est décrite dans « Migration de with_X vers loop » [1] peut être convertie en toute sécurité.

Ces derniers utilisent principalement des filtres.

  • Si vous avez besoin d'utiliser un plug-in de recherche pour convertir une construction with_* afin d'utiliser loop, il peut être plus clair de continuer à utiliser la syntaxe with_*.

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


IMPORTANT
Étant donné que loop ne procède pas à l'aplatissement implicite d'un niveau, il n'est pas exactement équivalent à with_items. Cependant, tant que la liste transmise à la boucle est une simple liste, les deux méthodes se comportent de manière identique. La distinction a de l'importance uniquement si vous disposez

d'une liste de listes.


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