If I run a playbook from start to finish, it gathers facts and then runs roles. But usually I don't do that: I just run roles directly (via their tags). So the facts are not gathered, and I get errors. To fix this I must remember to run a special "setup" task:
$ ansible-playbook playbook.yml -t setup,my-role
I often forget to do that, get errors and waste time. So I want each role to start with a fail-safe task that automatically gathers facts if necessary:
- setup:
when: ansible_os_family is undefined
That works. But in other questions I've read that not all facts are collected across all hosts - apparently there are differences.
I chose ansible_os_family
but I'm worried that it's not "universal".
Are there any facts that are 100% guaranteed to be collected across all hosts? (I don't need an exhaustive list, just a few, or even one, for this use case.)
Q: "I think ansible_os_family is a reasonable choice ... If you have a definitive reference, please add it as an answer."
Variable ansible_local
A: Create such a 'definitive' reference on your own. The setup module provides the parameter fact_path for this purpose. For example, test it on the localhost first. Create JSON file
shell> cat /etc/ansible/facts.d/misc.fact
{"run_setup": false}
The playbook
shell> cat pb.yml
- hosts: localhost
gather_facts: true
tasks:
- debug:
var: ansible_local
gives (abridged)
ansible_local:
misc:
run_setup: false
In your use case, you'll have to copy the file misc.fact to the remote hosts. Create a project for testing
shell> tree .
.
├── ansible.cfg
├── hosts
├── misc.fact
└── pb.yml
shell> cat ansible.cfg
[defaults]
gathering = explicit
collections_path = $HOME/.local/lib/python3.9/site-packages/
inventory = $PWD/hosts
roles_path = $PWD/roles
remote_tmp = ~/.ansible/tmp
retry_files_enabled = false
stdout_callback = yaml
shell> cat hosts
test_11
test_13
shell> cat misc.fact
{"run_setup": false}
Test it in a single play here to demonstrate the idea. In your use case, keep the block to manage the files either in the playbook or put it into the roles. Put the conditional setup into the roles.
shell> cat pb.yml
- hosts: all
gather_facts: false
pre_tasks:
- name: Manage ansible_local.misc facts
block:
- file:
state: directory
path: /etc/ansible/facts.d
- copy:
src: misc.fact
dest: /etc/ansible/facts.d/misc.fact
mode: 0644
become: true
- setup:
when: ansible_local.misc.run_setup|d(true)
tasks:
- debug:
var: ansible_local.misc.run_setup
gives
shell> ansible-playbook pb.yml
PLAY [all] ************************************************************************************
TASK [file] ***********************************************************************************
changed: [test_11]
changed: [test_13]
TASK [copy] ***********************************************************************************
changed: [test_13]
changed: [test_11]
TASK [setup] **********************************************************************************
ok: [test_13]
ok: [test_11]
TASK [debug] **********************************************************************************
ok: [test_11] =>
ansible_local.misc.run_setup: false
ok: [test_13] =>
ansible_local.misc.run_setup: false
PLAY RECAP ************************************************************************************
test_11: ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test_13: ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Cache facts
A: I still believe a better solution is to cache facts. Set DEFAULT_GATHERING smart
smart: each new host that has no facts discovered will be scanned, but if the same host is addressed in multiple plays it will not be contacted again in the run.
and enable fact cache plugin. For example,
shell> cat ansible.cfg
[defaults]
gathering = smart
#
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_cache.json
fact_caching_prefix = ansible_facts_
fact_caching_timeout = 86400
#
collections_path = $HOME/.local/lib/python3.9/site-packages/
inventory = $PWD/hosts
roles_path = $PWD/roles
remote_tmp = ~/.ansible/tmp
retry_files_enabled = false
stdout_callback = yaml
Given the project for testing
shell> tree .
.
├── ansible.cfg
├── hosts
├── pb.yml
└── roles
└── roleA
└── tasks
└── main.yml
shell> cat hosts
test_11
test_13
shell> cat roles/roleA/tasks/main.yml
- debug:
var: ansible_os_family
shell> cat pb.yml
- hosts: all
roles:
- roleA
The facts are gathered for the first time
shell> ansible-playbook pb.yml
PLAY [all] ************************************************************************************
TASK [Gathering Facts] ************************************************************************
ok: [test_11]
ok: [test_13]
TASK [roleA : debug] **************************************************************************
ok: [test_11] =>
ansible_os_family: FreeBSD
ok: [test_13] =>
ansible_os_family: FreeBSD
PLAY RECAP ************************************************************************************
test_11: ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test_13: ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
and cached
shell> tree /tmp/ansible_cache.json/
/tmp/ansible_cache.json/
├── ansible_facts_test_11
└── ansible_facts_test_13
Next time you run a playbook the cache is used
shell> ansible-playbook pb.yml
PLAY [all] ************************************************************************************
TASK [roleA : debug] **************************************************************************
ok: [test_11] =>
ansible_os_family: FreeBSD
ok: [test_13] =>
ansible_os_family: FreeBSD
PLAY RECAP ************************************************************************************
test_11: ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test_13: ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Notes:
You say you don't gather facts. You instead run the module setup after provisioning and then run other roles. This is the best use case for the described framework.
See other cache plugins
shell> ansible-doc -t cache -l
shell> cat roles/roleB/tasks/main.yml
- debug:
var: ansible_date_time.iso8601_micro
shell> cat roles/roleC/tasks/main.yml
- setup:
gather_subset: date_time
- debug:
var: ansible_date_time.iso8601_micro
shell> cat pb.yml
- hosts: all
roles:
- roleB
- roleC
a) Running repeatedly roleB and roleC. The first role uses the cached fact ansible_date_time. The last role updates the cache
shell> ansible-playbook -l test_11 pb.yml
PLAY [all] ************************************************************************************
TASK [roleB : debug] **************************************************************************
ok: [test_11] =>
ansible_date_time.iso8601_micro: '2023-06-20T08:10:56.219945Z'
TASK [roleC : setup] **************************************************************************
ok: [test_11]
TASK [roleC : debug] **************************************************************************
ok: [test_11] =>
ansible_date_time.iso8601_micro: '2023-06-20T08:11:15.289719Z'
PLAY RECAP ************************************************************************************
test_11: ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
shell> ansible-playbook -l test_11 pb.yml
PLAY [all] ************************************************************************************
TASK [roleB : debug] **************************************************************************
ok: [test_11] =>
ansible_date_time.iso8601_micro: '2023-06-20T08:11:15.289719Z'
TASK [roleC : setup] **************************************************************************
ok: [test_11]
TASK [roleC : debug] **************************************************************************
ok: [test_11] =>
ansible_date_time.iso8601_micro: '2023-06-20T08:11:39.579222Z'
PLAY RECAP ************************************************************************************
test_11: ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
b) Set ANSIBLE_CACHE_PLUGIN_TIMEOUT=-1
if you want to update the cache
shell> ANSIBLE_CACHE_PLUGIN_TIMEOUT=-1 ansible-playbook -l test_11 pb.yml
PLAY [all] ************************************************************************************
TASK [roleB : debug] **************************************************************************
ok: [test_11] =>
ansible_date_time.iso8601_micro: '2023-06-20T08:28:40.088411Z'
TASK [roleC : setup] **************************************************************************
ok: [test_11]
TASK [roleC : debug] **************************************************************************
ok: [test_11] =>
ansible_date_time.iso8601_micro: '2023-06-20T08:28:43.891752Z'
PLAY RECAP ************************************************************************************
test_11: ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0