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 tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn