
Quand aucun module dédié n’existe, vous tombez sur les quatre modules d’exécution directe : raw, command, shell, script. Ils ne sont pas idempotents par nature et ouvrent la porte à des pièges sécurité (injection, redirections, état imprévu).
Cette page donne le mode d’emploi pragmatique : quand utiliser lequel, comment retrouver l’idempotence avec creates: / removes: / changed_when:, et pourquoi un module dédié reste toujours préférable.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Choisir entre
raw,command,shell,scriptselon le contexte. - Restaurer l’idempotence avec
creates:,removes:,changed_when:. - Éviter les injections de commandes via shell.
- Capturer les sorties (
register:) pour conditionner les tâches suivantes.
Prérequis
Section intitulée « Prérequis »- Connaître les bases d’un playbook (
tasks:,register:,when:). - Avoir déjà rencontré l’un de ces modules dans du code legacy.
Tableau de décision
Section intitulée « Tableau de décision »| Module | Quand l’utiliser | Idempotence | Shell |
|---|---|---|---|
raw | Cible sans Python (routeur, embedded, bootstrap) | Non | Non |
command | Commande simple, pas de pipe ni redirection | creates: / removes: | Non |
shell | Pipes ` | , redirections >, opérateurs &&` | creates: / removes: |
script | Lancer un script local sur la cible distante | creates: / removes: | Oui |
Règle absolue : essayer dans l’ordre package → module dédié → command → shell. raw en dernier.
Module raw — bootstrap minimal
Section intitulée « Module raw — bootstrap minimal »- name: Installer Python sur une cible bare-metal ansible.builtin.raw: dnf -y install python3Cas d’usage unique : la machine cible n’a pas Python, donc les autres modules ne fonctionnent pas. Typique du bootstrap d’une fresh install ou d’un équipement réseau (Cisco IOS, switches).
Limites : pas de structuration de la sortie, pas de creates:, pas d’idempotence. À utiliser sur 1 ou 2 tâches max pour amorcer Python, puis basculer sur les modules normaux.
Module command — la sécurité d’abord
Section intitulée « Module command — la sécurité d’abord »- name: Generer un certificat (sans pipe ni redirection) ansible.builtin.command: cmd: openssl req -x509 -newkey rsa:4096 -keyout /etc/ssl/key.pem -out /etc/ssl/cert.pem -days 365 -nodes -subj "/CN=db1.lab" creates: /etc/ssl/cert.pemPas de shell = pas d’interprétation des $VAR, des |, des >. Plus sûr car pas d’injection possible via une variable Jinja mal échappée. Préféré à shell: chaque fois que c’est possible.
creates: /etc/ssl/cert.pem = la commande ne s’exécute que si le fichier n’existe pas. Restaure l’idempotence sans changer le module.
Module shell — quand vous avez besoin de pipes
Section intitulée « Module shell — quand vous avez besoin de pipes »- name: Compter les lignes d'erreur dans le log ansible.builtin.shell: grep ERROR /var/log/app.log | wc -l register: error_count changed_when: falsechanged_when: false = la tâche est marquée ok, jamais changed. Indispensable pour les commandes de lecture pure — sinon Ansible affiche changed à chaque run et pourrit votre rapport.
Risque sécurité : si une variable Jinja arrive dans la commande (grep "{{ user_input }}" file), un ; ou un && dans la valeur peut exécuter du code arbitraire. Toujours valider l’input ou utiliser command: avec une liste d’arguments.
Module script — lancer un script local sur la cible
Section intitulée « Module script — lancer un script local sur la cible »- name: Initialiser la base si elle n'existe pas ansible.builtin.script: files/init_db.sh --env prod args: creates: /var/lib/app/db.sqliteDifférence avec command: : script: transfère le fichier depuis le control node vers la cible avant de l’exécuter, puis le supprime automatiquement. Pas besoin de copy: + command: en deux étapes.
Paramètres utiles :
creates:/removes:: idempotence basée sur l’existence d’un fichier.chdir:: changer de répertoire avant exécution.executable:: forcer un interpréteur (python3,bash).
Restaurer l’idempotence
Section intitulée « Restaurer l’idempotence »Trois mécanismes pour faire d’une commande non-idempotente une tâche propre :
- name: Extraire une archive (idempotent via creates) ansible.builtin.command: cmd: tar xzf /tmp/archive.tar.gz -C /opt creates: /opt/extracted/marker
- name: Supprimer un fichier temporaire (idempotent via removes) ansible.builtin.command: cmd: rm /tmp/tempfile removes: /tmp/tempfile
- name: Lecture seule — ne jamais marquer changed ansible.builtin.shell: cat /etc/hostname register: hostname_out changed_when: falseGérer failed_when: proprement
Section intitulée « Gérer failed_when: proprement »- name: Lire un log qui peut etre absent ansible.builtin.shell: cat /var/log/maybe.log register: log_content failed_when: false changed_when: falsefailed_when: false = ne pas faire échouer le play même si rc != 0. À combiner avec un test sur result.rc plus loin si besoin.
Pièges courants
Section intitulée « Pièges courants »| Symptôme | Cause | Fix |
|---|---|---|
changed à chaque run sur une commande de lecture | Pas de changed_when: | changed_when: false |
ansible.builtin.command rejette `cmd | grep` | Pas de shell, pas de pipe |
| Injection via variable Jinja | shell: + variable non validée | Préférer command: avec une liste, ou échapper avec `{{ var |
script: ne trouve pas le fichier | Chemin relatif non résolu | Préfixer par files/ (résolu relativement au playbook/rôle) |
| Idempotence cassée à chaque run | Pas de creates: ou removes: | Identifier un artefact persistant et le déclarer |
À retenir
Section intitulée « À retenir »- Essayer un module dédié avant chaque
command:oushell:. command:>shell:quand pas de pipe — moins de surface d’attaque.creates:/removes:= restauration d’idempotence à coût zéro.changed_when: falsesur toute commande de lecture pure.raw:= bootstrap uniquement, jamais en mode normal.script:=copy + commanden une tâche, idéal pour les scripts ad-hoc.