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