Script Shell Et Ansible : Arguments Dynamiques En Boucle
Salut les gars ! Aujourd'hui, on va plonger dans un truc super cool avec Ansible : utiliser la puissance des scripts shell pour alimenter dynamiquement vos arguments Ansible, le tout dans une boucle. C'est comme donner un coup de pouce à vos playbooks pour qu'ils soient plus intelligents et plus adaptables. On va décortiquer ça étape par étape, et vous allez voir, c'est pas sorcier !
L'Art d'Injecter des Données Dynamiques dans Ansible
Vous savez, les gars, l'un des aspects les plus puissants d'Ansible, c'est sa capacité à automatiser des tâches répétitives sur de nombreuses machines. Mais parfois, les données dont vous avez besoin pour ces tâches ne sont pas statiques. Elles peuvent changer, elles peuvent dépendre de l'état actuel de votre système, ou elles peuvent même provenir de la sortie d'un autre outil. C'est là que notre astuce du jour entre en jeu. Imaginez que vous ayez besoin de redimensionner des partitions de disque, mais que les identifiants de ces disques (comme les UUID) ne soient pas connus à l'avance. Ou peut-être que vous devez créer des utilisateurs dont les noms proviennent d'un fichier texte dynamique. C'est exactement le genre de scénario où l'on veut utiliser la sortie d'un script shell comme argument pour une tâche Ansible, surtout quand on doit répéter cette opération sur plusieurs éléments grâce à une boucle. L'exemple que nous allons explorer concerne le redimensionnement de systèmes de fichiers XFS, où l'identifiant du disque (dev) n'est pas fixe. On utilise une variable disks qui est une liste, et pour chaque élément de cette liste, on veut appliquer une tâche de redimensionnement. Mais si, au lieu d'avoir une liste pré-définie, on devait d'abord récupérer la liste des disques à redimensionner via un script shell ? C'est là que ça devient vraiment intéressant et que la flexibilité d'Ansible brille. On ne se contente plus d'exécuter des commandes ; on construit des flux de travail intelligents qui s'adaptent à leur environnement. Pensez à toutes les possibilités ! Cela ouvre la porte à des automatisations bien plus complexes et robustes, capables de gérer des environnements IT hétérogènes et évolutifs sans avoir à modifier manuellement vos playbooks à chaque petite variation. C'est la promesse d'une infrastructure véritablement auto-gérante et auto-adaptative, un rêve pour tout administrateur système, n'est-ce pas ? Alors, restez connectés, car on va mettre les mains dans le cambouis pour y arriver.
Le Cas Pratique : Redimensionnement de Disques avec XFS
Pour notre exemple, les gars, on va s'attaquer au redimensionnement de systèmes de fichiers XFS. Supposez que vous ayez une liste de disques que vous voulez redimensionner, et pour chaque disque, vous avez son UUID. Votre tâche Ansible pourrait ressembler à ceci :
- name: Resize xfs
ansible.builtin.filesystem:
fstype: xfs
dev: "{{ item.uuid }}"
resizefs: yes
loop: "{{ disks }}"
Ici, disks serait une structure de données Ansible (probablement une liste de dictionnaires), où chaque dictionnaire contiendrait au moins une clé uuid pointant vers l'UUID du disque à traiter. C'est déjà pas mal, mais imaginez que la liste disks ne soit pas quelque chose que vous connaissez d'avance. Peut-être que vous devez scanner le système pour trouver quels disques doivent être redimensionnés, ou peut-être que cette information provient d'un autre processus. C'est là que le script shell devient notre meilleur ami. Au lieu de coder en dur la liste disks, on pourrait avoir un script shell qui, par exemple, liste tous les disques non montés, ou tous les disques qui dépassent une certaine taille et nécessitent une extension. La sortie de ce script pourrait alors être formatée d'une manière qu'Ansible peut comprendre, et pouf, on a notre liste dynamique !
Pour être plus concret, notre script shell pourrait chercher des disques qui ont une certaine caractéristique (par exemple, des disques secondaires ajoutés mais pas encore formatés ou redimensionnés) et ensuite retourner une liste d'UUID. Ansible, lui, utiliserait ensuite cette liste pour exécuter la tâche filesystem sur chacun d'eux. C'est ce qu'on appelle le déploiement dynamique et conditionnel, et c'est hyper utile pour gérer des parcs de serveurs où la configuration peut varier. On n'a plus besoin de savoir à l'avance tous les détails. On dit à Ansible : "Va chercher l'information nécessaire, et ensuite, fais ceci pour chaque élément trouvé." Cette approche rend vos playbooks incroyablement flexibles. Ils peuvent s'adapter à des environnements où les ressources changent fréquemment, comme dans le cloud ou dans des clusters conteneurisés. C'est un gain de temps et de robustesse phénoménal. Pensez à la maintenance : plus besoin de mettre à jour des listes statiques dans vos fichiers de variables. Le système s'auto-configure en quelque sorte. C'est l'automatisation à son apogée.
L'Intégration du Script Shell avec Ansible : Le "How-To"
Alors, comment on fait pour que la sortie de notre script shell devienne l'argument de notre boucle Ansible, les potos ? La méthode la plus courante et la plus propre consiste à utiliser le module command ou shell d'Ansible pour exécuter le script, puis à capturer sa sortie pour la stocker dans une variable. Ensuite, on utilise cette variable dans notre boucle. Regardons ça de plus près.
Étape 1 : Le Script Shell qui fait le boulot
D'abord, créons un script shell fictif (appelons-le get_disks_to_resize.sh) qui va nous retourner une liste d'UUIDs, un par ligne. Par exemple :
#!/bin/bash
# Supposons que ce script trouve les disques qui doivent être redimensionnés
# et retourne leurs UUIDs, un par ligne.
echo "UUID_DU_DISQUE_1"
echo "UUID_DU_DISQUE_2"
echo "UUID_DU_DISQUE_3"
Ce script est une simulation. Dans la vraie vie, il pourrait utiliser des commandes comme lsblk, blkid, fdisk -l, ou des outils spécifiques à votre cloud provider pour identifier les disques. L'important est qu'il produise une sortie simple et structurée. Pour qu'Ansible puisse facilement l'utiliser, une sortie par ligne est idéale.
Étape 2 : Exécuter le Script et Capturer la Sortie dans Ansible
Maintenant, dans votre playbook Ansible, vous allez utiliser le module command (ou shell si votre script utilise des pipes ou des redirections complexes) pour exécuter ce script et stocker son résultat dans une variable. Il est crucial de bien gérer la sortie. Souvent, la sortie brute contient des sauts de ligne indésirables ou des espaces. On va vouloir nettoyer ça.
- name: Exécuter le script pour obtenir les UUIDs des disques
ansible.builtin.command: "/path/to/your/get_disks_to_resize.sh"
register: disk_uuids_output
changed_when: false # On ne veut pas que cette tâche soit marquée comme 'changed' si le script réussit
- name: Préparer la liste des UUIDs pour la boucle
ansible.builtin.set_fact:
disks_to_resize: "{{ disk_uuids_output.stdout_lines }}"
Dans cet extrait, on exécute notre script. Le résultat brut (la sortie standard) est stocké dans disk_uuids_output. register: disk_uuids_output fait exactement cela. Ensuite, on utilise stdout_lines. C'est une variable spéciale enregistrée par Ansible qui contient la sortie standard du script, divisée en une liste de chaînes, où chaque chaîne est une ligne de la sortie originale. C'est parfait pour notre boucle ! changed_when: false est une bonne pratique ici car l'exécution d'un script qui ne modifie pas le système ne devrait pas être signalée comme un changement par Ansible.
La tâche set_fact est ensuite utilisée pour créer une nouvelle variable, disks_to_resize, qui contiendra directement la liste des UUIDs nettoyée et prête à l'emploi. C'est une étape clé pour s'assurer que les données sont dans le bon format avant de les passer à la boucle. On pourrait aussi faire un peu plus de nettoyage si nécessaire, par exemple en retirant d'éventuels caractères de retour chariot ou en s'assurant que chaque ligne est bien un UUID valide, mais stdout_lines est souvent suffisant pour commencer.
Étape 3 : Utiliser la Nouvelle Variable dans la Boucle
Maintenant que nous avons notre liste disks_to_resize, on peut l'utiliser dans notre boucle. On va adapter la tâche initiale. Si disks_to_resize est une liste d'UUIDs simples (chaînes de caractères), notre boucle devra être légèrement modifiée pour s'attendre à cela.
- name: Resize xfs sur les disques identifiés
ansible.builtin.filesystem:
fstype: xfs
dev: "{{ item }}" # Utilise directement l'UUID de la boucle
resizefs: yes
loop: "{{ disks_to_resize }}"
when: disks_to_resize | length > 0 # S'assurer qu'il y a bien des disques Ă traiter
Ici, on utilise {{ item }} directement, car disks_to_resize est maintenant une liste d'UUIDs. Le when: disks_to_resize | length > 0 est une petite astuce de sécurité : cette tâche ne s'exécutera que s'il y a effectivement des UUIDs dans notre liste. Si le script shell ne retourne rien, la boucle ne s'exécutera pas, évitant ainsi des erreurs potentielles. C'est super propre, non ? On a réussi à rendre notre tâche de redimensionnement dynamique grâce à un script externe.
Cette approche est extrêmement flexible. Vous pouvez changer la logique du script shell sans toucher au playbook Ansible principal. Par exemple, si vous décidez de redimensionner non seulement les disques XFS, mais aussi les disques ext4, il vous suffira de modifier le script shell pour qu'il retourne tous les UUIDs concernés, et votre playbook Ansible continuera de fonctionner sans modification. C'est le rêve de l'automatisation, les gars !
Aller plus loin : Gestion d'Erreurs et Cas Complexes
Bien sûr, les gars, le monde réel est rarement aussi simple. Que se passe-t-il si notre script shell échoue ? Ou si la sortie n'est pas exactement ce qu'on attend ? Ansible offre des mécanismes pour gérer ça.
Gestion des Échecs du Script Shell
Si votre script shell retourne un code de sortie non nul, Ansible considérera par défaut que la tâche a échoué. Vous pouvez utiliser failed_when pour définir des conditions d'échec personnalisées. Par exemple, si vous voulez qu'une tâche échoue uniquement si la sortie contient le mot "ERROR", vous pourriez faire :
- name: Exécuter le script et gérer les erreurs
ansible.builtin.command: "/path/to/your/get_disks_to_resize.sh"
register: disk_uuids_output
changed_when: false
failed_when: "disk_uuids_output.rc != 0 or 'ERROR' in disk_uuids_output.stdout"
Ici, la tâche échoue si le code de retour (rc) n'est pas zéro ou si le mot "ERROR" apparaît dans la sortie standard. C'est une façon robuste de s'assurer que vous êtes alerté en cas de problème.
Traitement de Sorties Complexes
Parfois, la sortie de votre script n'est pas juste une liste de chaînes. Elle pourrait être du JSON, du CSV, ou un format plus complexe. Ansible est plutôt bon pour gérer ça.
-
JSON : Si votre script shell sort du JSON, vous pouvez utiliser le filtre
from_jsond'Ansible. Par exemple, si le script retourne{"disks": [{"uuid": "..."}, {"uuid": "..."}]}, vous pourriez faire :- name: Exécuter script et parser le JSON ansible.builtin.command: "/path/to/json_script.sh" register: json_output changed_when: false - name: Préparer la liste des disques à partir du JSON ansible.builtin.set_fact: disks_to_resize: "{{ json_output.stdout | from_json | map(attribute='uuid') | list }}"Ici, on utilise
from_jsonpour parser la sortie, puismap(attribute='uuid')pour extraire uniquement les UUIDs, et enfinlistpour s'assurer que le résultat est une liste. -
CSV : Pour du CSV, on peut utiliser des filtres comme
splitetregex_search. Par exemple, si la sortie estUUID,SIZE uuid1,100G uuid2,200G, on pourrait extraire les UUIDs comme ceci :- name: Préparer la liste des disques à partir du CSV ansible.builtin.set_fact: disks_to_resize: "{{ csv_output.stdout_lines | map('split', ',') | map(attribute='0') | list }}"Ici, on prend chaque ligne, on la divise par la virgule (
split(',')), puis on prend le premier élément (attribute='0').
Ces exemples montrent à quel point Ansible peut être puissant pour intégrer des données externes. Le module debug est aussi votre ami pour inspecter les variables et leur contenu à chaque étape.
L'Importance de la Pureté des Scripts
Un conseil d'ami, les gars : essayez de rendre vos scripts shell aussi