ansibleansible-facts

How to ad-hoc filter hosts (target Managed Nodes) in Ansible using facts on CLI level?


On SaltStack one can execute:

salt -G 'os:CentOS' test.version

-G means filter managed nodes using grains. SaltStack's Grains is an equivalent of Ansible's facts. SaltStack is caching grains on controller, so this functionality is not a problem. Ansible has some facts caching too but it looks like there is not much use of it. Is something like this possible with Ansible?

I know it can be done using custom dynamic inventory modules or by embedding some code inside playbook, but I'm looking for something more ad-hoc.


Solution

  • Q: "Is something like this possible with Ansible?"

    It is possible to get results slightly based on the behavior. If Caching facts is enabled one could use them for conditional ad-hoc commands.

    ansible test --ask-pass --ask-become-pass --become --module-name shell --args '{% if ansible_facts.distribution == "RedHat" %}cat /etc/redhat-release{% else%}exit 0{% endif %}'
    
    SSH password:
    BECOME password[defaults to SSH password]:
    redhat.example.com | CHANGED | rc=0 >>
    Red Hat Enterprise Linux release 8.10 (Ootpa)
    centos.example.com | CHANGED | rc=0 >>
    

    But as one can see, it would run the command against all hosts in the group and not the subset based on host properties, cached facts. The execution becomes filtered during runtime on the Remote Nodes within the task.

    Q: "I know it can be done using custom dynamic inventory modules or by embedding some code inside playbook, but I'm looking for something more ad-hoc."

    Ansible has the capability for an ad-hoc inventory. It is the ,. The command syntax ansible --inventory list,of,hosts, all will treat the given string as list of hosts instead of a file to look up for content. This works because the simplest inventory is a list of hosts.

    Therefore one could simply use the cached facts to generate such a list. In example

    # Lookup the cached facts for the requested properties
    grep -l 'ansible_distribution: RedHat' /tmp/ansible/facts_cache/*
    /tmp/ansible/facts_cache/one.example.com
    /tmp/ansible/facts_cache/two.example.com
    
    # Gather the hostname(s) only
    grep -l 'ansible_distribution: RedHat' /tmp/ansible/facts_cache/* | xargs -L 1 basename
    one.example.com
    two.example.com
    
    # Create the dynamic list of hosts
    grep -l 'ansible_distribution: RedHat' /tmp/ansible/facts_cache/* | xargs -L 1 basename | uniq | tr '\n' ','
    one.example.com,two.example.com,
    

    "Ansible has some facts caching too but it looks like there is not much use of it."

    With this approach one can run an Ansible ad-hoc command just on the necessary hosts based on ansible_facts.

    ansible -i "$(grep -l 'ansible_distribution: RedHat' /tmp/ansible/facts_cache/* | xargs -L 1 basename | uniq | tr '\n' ',')" all -m shell -a 'hostname'
    

    The execution becomes filtered before task runtime and on the Control Node.

    To summarize, the equivalent to Salt grains seems to be

    --inventory "$(grep -l "${FACT}: ${PROPERTY}" ${FACT_CACHING_PATH}/* | xargs -L 1 basename | tr '\n' ',')" all
    

    With that, an alias or wrapper script can be easily implemented.

    Furthermore, depending on how the fact caching is configured in ansible.cfg

    [defaults]
    ...
    fact_path               = /etc/ansible/facts.d
    fact_caching            = yaml # or jsonfile
    fact_caching_connection = /tmp/ansible/facts_cache
    

    and what one like to implement, instead of grep, either yp or jq could be used, or even Python.

    Further Documentation Reading