Aller au contenu
Infrastructure as Code medium

Custom facts Ansible : facts.d/*.fact, INI vs script Bash, ansible_local

10 min de lecture

Logo Ansible

Les facts standards (ansible_distribution, ansible_default_ipv4, ansible_memtotal_mb, etc.) sont collectés par le module setup au début de chaque play. Les custom facts étendent ce mécanisme : on dépose un script (Bash, Python ou JSON/INI statique) dans /etc/ansible/facts.d/<nom>.fact sur la cible, et Ansible le lit à chaque gather_facts: true puis expose le résultat sous ansible_local.<nom>.

À la fin de cette page, vous saurez créer deux types de custom facts (statique INI + dynamique script Bash → JSON), les lire dans un playbook, et choisir entre custom facts, set_fact et host_vars selon le cas d'usage.

  • Déposer un custom fact statique au format INI dans /etc/ansible/facts.d/.
  • Déposer un custom fact dynamique (script Bash exécutable retournant du JSON).
  • Lire un custom fact via ansible_local.<nom> dans un playbook.
  • Filtrer la sortie de setup pour ne garder que les ansible_local.
  • Choisir entre custom facts, set_fact, host_vars, inventaire dynamique.

Trois cas d'usage récurrents :

  • Tagger un hôte avec son rôle métier (web, db, cache, monitoring) sans toucher à l'inventaire centralisé.
  • Exposer des données spécifiques à une application : version déployée, hash du dernier commit, statut d'une migration de base.
  • Centraliser des informations propriétaires : numéro de série hardware, contrat de support, compartiment cloud.

L'avantage : la vérité vit côté cible (la machine sait elle-même son rôle). Le control node n'a rien à savoir, il lit au lieu de déclarer.

Ansible accepte deux formats dans /etc/ansible/facts.d/<nom>.fact :

FormatDétectionCas d'usage
Statique (INI, YAML, JSON)Fichier non exécutable (mode 0644)Données figées au déploiement
Dynamique (script)Fichier exécutable (mode 0755)Données qui changent (uptime, load, statut service)

🔍 Observation cruciale : le bit exécutable (+x) est ce qui distingue les deux. Avec 0755, Ansible exécute le script et parse la sortie. Avec 0644, Ansible lit le fichier comme statique.

; /etc/ansible/facts.d/server.fact (mode 0644)
[meta]
role = database
environment = production
managed_by = ansible
[deployment]
version = 1.4.2
deployed_on = 2026-04-27

Via Ansible, depuis le control node :

- hosts: db1.lab
become: true
tasks:
- name: Créer /etc/ansible/facts.d/
ansible.builtin.file:
path: /etc/ansible/facts.d
state: directory
mode: "0755"
- name: Déposer le custom fact statique
ansible.builtin.copy:
dest: /etc/ansible/facts.d/server.fact
mode: "0644"
content: |
[meta]
role = database
environment = production
[deployment]
version = 1.4.2
Fenêtre de terminal
ansible db1.lab -m ansible.builtin.setup -a "filter=ansible_local"

Sortie :

ansible_local:
server:
deployment:
version: "1.4.2"
meta:
environment: production
role: database

🔍 Observation : la structure est ansible_local.<nom_fichier_sans_.fact>.<section>.<clé>. Le fichier server.fact produit la racine ansible_local.server, les sections INI deviennent des sous-clés. Filter ansible_local isole les custom facts du reste.

- hosts: db1.lab
gather_facts: true # ← obligatoire pour collecter ansible_local
tasks:
- ansible.builtin.copy:
dest: /tmp/config.txt
content: |
Hostname: {{ inventory_hostname }}
Role: {{ ansible_local.server.meta.role }}
Env: {{ ansible_local.server.meta.environment }}
Version: {{ ansible_local.server.deployment.version }}
mode: "0644"
#!/bin/bash
# /etc/ansible/facts.d/uptime.fact (mode 0755 EXÉCUTABLE)
cat <<EOF
{
"uptime_seconds": $(awk '{print int($1)}' /proc/uptime),
"load_1min": "$(awk '{print $1}' /proc/loadavg)",
"kernel": "$(uname -r)"
}
EOF

Via Ansible :

- name: Déposer le fact dynamique
ansible.builtin.copy:
dest: /etc/ansible/facts.d/uptime.fact
mode: "0755" # ← BIT EXÉCUTABLE OBLIGATOIRE
content: |
#!/bin/bash
cat <<EOF
{
"uptime_seconds": $(awk '{print int($1)}' /proc/uptime),
"kernel": "$(uname -r)"
}
EOF

Lecture (après setup avec filter=ansible_local) :

ansible_local:
uptime:
kernel: "5.14.0-..."
uptime_seconds: 3242

🔍 Observation : Ansible détecte automatiquement le bit +x. Si oui, exécute le script et parse la sortie comme JSON. Format alternatif accepté : YAML, INI. Le script peut être en Python, Perl, ou n'importe quel langage exécutable.

MécanismeStockagePersistanceCas d'usage
Custom fact (facts.d/*.fact)Sur la ciblePersistant entre runsTag métier, rôle, version déployée
set_factEn mémoire pendant le playDétruit en fin de playCalcul intermédiaire dans un playbook
host_vars/<host>.ymlSur le control node (Git)Versionné dans le repoConfig statique connue à l'avance
Inventaire dynamiqueSource externe (Cloud, DB)Re-collecté à chaque runCloud, K8s, NetBox, infra dynamique

Décision pratique : custom facts = vérité côté cible. host_vars = vérité côté gestion. Les deux peuvent coexister. Custom facts brillent pour le bootstrap (la machine s'auto-déclare son rôle dès le cloud-init).

Le lab ecrire-code/custom-facts (labs/ecrire-code/custom-facts/) couvre les deux formats (INI statique + script Bash dynamique) avec 8 tests pytest qui valident le mode 0644 vs 0755, la structure ansible_local, et la combinaison des deux dans un playbook.

  • Bit exécutable oublié : 0644 sur un script → Ansible le lit comme statique → parsing échoue (le shebang #!/bin/bash n'est pas du JSON).
  • gather_facts: false : ansible_local reste vide. Soit activer gather_facts: true, soit appeler setup: explicitement après dépôt.
  • Nom de fichier avec - : lab14a-uptime.fact → accessible via ansible_local['lab14a-uptime'] (notation crochets, pas point).
  • Permissions de /etc/ansible/facts.d/ : doit être 0755 accessible en lecture par tout user qui lance Ansible (ou root si become: true).
  • /etc/ansible/facts.d/<nom>.fact = chemin par défaut.
  • Format : INI/YAML/JSON statique OU script exécutable retournant du JSON.
  • Lecture : ansible_local.<nom>.<section>.<clé> après gather_facts: true.
  • Bit exécutable (mode: 0755) → script. Sinon (0644) → statique.
  • ansible -m setup -a "filter=ansible_local" isole les custom facts.
  • Pas testé directement à l'EX294 mais utile en prod (tagging d'inventaire).

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