Aller au contenu
Infrastructure as Code medium

Modules raw, command, shell, script Ansible : exécution directe en dernier recours

6 min de lecture

Logo Ansible

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.

  • Choisir entre raw, command, shell, script selon 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.
  • Connaître les bases d’un playbook (tasks:, register:, when:).
  • Avoir déjà rencontré l’un de ces modules dans du code legacy.
ModuleQuand l’utiliserIdempotenceShell
rawCible sans Python (routeur, embedded, bootstrap)NonNon
commandCommande simple, pas de pipe ni redirectioncreates: / removes:Non
shellPipes `, redirections >, opérateurs &&`creates: / removes:
scriptLancer un script local sur la cible distantecreates: / removes:Oui

Règle absolue : essayer dans l’ordre package → module dédié → commandshell. raw en dernier.

- name: Installer Python sur une cible bare-metal
ansible.builtin.raw: dnf -y install python3

Cas 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.

- 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.pem

Pas 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.

- 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: false

changed_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.sqlite

Diffé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).

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: false
- name: Lire un log qui peut etre absent
ansible.builtin.shell: cat /var/log/maybe.log
register: log_content
failed_when: false
changed_when: false

failed_when: false = ne pas faire échouer le play même si rc != 0. À combiner avec un test sur result.rc plus loin si besoin.

SymptômeCauseFix
changed à chaque run sur une commande de lecturePas de changed_when:changed_when: false
ansible.builtin.command rejette `cmdgrep`Pas de shell, pas de pipe
Injection via variable Jinjashell: + variable non validéePréférer command: avec une liste, ou échapper avec `{{ var
script: ne trouve pas le fichierChemin relatif non résoluPréfixer par files/ (résolu relativement au playbook/rôle)
Idempotence cassée à chaque runPas de creates: ou removes:Identifier un artefact persistant et le déclarer
  • Essayer un module dédié avant chaque command: ou shell:.
  • command: > shell: quand pas de pipe — moins de surface d’attaque.
  • creates: / removes: = restauration d’idempotence à coût zéro.
  • changed_when: false sur toute commande de lecture pure.
  • raw: = bootstrap uniquement, jamais en mode normal.
  • script: = copy + command en une tâche, idéal pour les scripts ad-hoc.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn