Bookmark this page

Managing Facts

Objectives

  • Reference data about managed hosts using Ansible facts, and configure custom facts on managed hosts.

Describing Ansible Facts

Ansible facts are variables that are automatically discovered by Ansible on a managed host. Facts contain host-specific information that can be used just like regular variables in plays, conditionals, loops, or any other statement that depends on a value collected from a managed host.

Some facts gathered for a managed host might include:

  • The host name

  • The kernel version

  • Network interface names

  • Network interface IP addresses

  • Operating system version

  • Number of CPUs

  • Available or free memory

  • Size and free space of storage devices

You can even create custom facts, which are stored on the managed host and are unique to that system.

Facts are a convenient way to retrieve the state of a managed host and to determine what action to take based on that state. For example:

  • Your play might restart a server by using a conditional task based on the value of a fact that was gathered, such as the status of a particular service.

  • The play might customize a MySQL configuration file depending on the available memory that is reported by a fact.

  • The IPv4 address used in a configuration file might be set based on the value of a fact.

Normally, every play runs the ansible.builtin.setup module automatically to gather facts, before it performs its first task.

This is reported as the Gathering Facts task in Ansible 2.3 and later, or simply as setup in earlier versions of Ansible. By default, you do not need to have a task to run ansible.builtin.setup in your play. It is normally run automatically for you.

One way to see what facts are gathered for your managed hosts is to run a short playbook that gathers facts and uses the ansible.builtin.debug module to print the value of the ansible_facts variable.

- name: Fact dump
  hosts: all
  tasks:
    - name: Print all facts
      ansible.builtin.debug:
        var: ansible_facts

When you run the playbook, the facts are displayed in the job output:

[user@demo ~]$ ansible-navigator run -m stdout facts.yml

PLAY [Fact dump] ***************************************************************

TASK [Gathering Facts] *********************************************************
ok: [demo1.example.com]

TASK [Print all facts] *********************************************************
ok: [demo1.example.com] => {
    "ansible_facts": {
        "all_ipv4_addresses": [
            "10.30.0.178",
            "172.25.250.10"
        ],
        "all_ipv6_addresses": [
            "fe80::8389:96fd:e53e:979",
            "fe80::cb51:6814:6342:7bbc"
        ],
        "ansible_local": {}
            }
        },
        "apparmor": {
            "status": "disabled"
        },
        "architecture": "x86_64",
        "bios_date": "04/01/2014",
        "bios_vendor": "SeaBIOS",
        "bios_version": "1.13.0-2.module+el8.2.1+7284+aa32a2c4",
        "board_asset_tag": "NA",
        "board_name": "NA",
        "board_serial": "NA",
        "board_vendor": "NA",
        "board_version": "NA",
        "chassis_asset_tag": "NA",
        "chassis_serial": "NA",
        "chassis_vendor": "Red Hat",
        "chassis_version": "RHEL 7.6.0 PC (i440FX + PIIX, 1996)",
        "cmdline": {
            "BOOT_IMAGE": "(hd0,gpt3)/vmlinuz-5.14.0-70.13.1.el9_0.x86_64",
            "console": "ttyS0,115200n8",
            "crashkernel": "1G-4G:192M,4G-64G:256M,64G-:512M",
            "net.ifnames": "0",
            "no_timer_check": true,
            "root": "UUID=fb535add-9799-4a27-b8bc-e8259f39a767"
        },
...output omitted...

The playbook displays the content of the ansible_facts variable in JSON format as a dictionary of variables. You can browse the output to see what facts are gathered, and to find facts that you might want to use in your plays.

The following table shows some facts that might be gathered from a managed node and which might be useful in a playbook:

Table 3.3. Examples of Ansible Facts

FactVariable
Short hostname ansible_facts['hostname']
Fully qualified domain name ansible_facts['fqdn']
Main IPv4 address (based on routing) ansible_facts['default_ipv4']['address']
List of the names of all network interfaces ansible_facts['interfaces']
Size of the /dev/vda1 disk partition ansible_facts['devices']['vda']['partitions']['vda1']['size']
List of DNS servers ansible_facts['dns']['nameservers']
Version of the currently running kernel ansible_facts['kernel']

Note

Remember that when a variable's value is a dictionary, one of two syntaxes can be used to retrieve the value. To take two examples from the preceding table:

  • ansible_facts['default_ipv4']['address'] can also be written ansible_facts.default_ipv4.address

  • ansible_facts['dns']['nameservers'] can also be written ansible_facts.dns.nameservers

When a fact is used in a playbook, Ansible dynamically substitutes the variable name for the fact with the corresponding value:

---
- hosts: all
  tasks:
  - name: Prints various Ansible facts
    ansible.builtin.debug:
      msg: >
        The default IPv4 address of {{ ansible_facts.fqdn }}
        is {{ ansible_facts.default_ipv4.address }}

The following output shows how Ansible was able to query the managed node and dynamically use the system information to update the variable. You can also use facts to create dynamic groups of hosts that match particular criteria.

[user@demo ~]$ ansible-navigator run -m stdout playbook.yml
PLAY [all] ***********************************************************************

TASK [Gathering Facts] *****************************************************
ok: [demo1.example.com]

TASK [Prints various Ansible facts] ****************************************
ok: [demo1.example.com] => {
    "msg": "The default IPv4 address of demo1.example.com is 172.25.250.10\n"
}

PLAY RECAP *****************************************************************
demo1.example.com    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Ansible Facts Injected as Variables

Before Ansible 2.5, facts were always injected as individual variables prefixed with the string ansible_ instead of being part of the ansible_facts variable. For example, the ansible_facts['distribution'] fact was called ansible_distribution.

Many playbooks still use facts injected as variables instead of the new syntax, which uses the ansible_facts.* namespace.

One reason why the Ansible community discourages injecting facts as variables is because it risks unexpected collisions between facts and variables. A fact has a very high precedence that overrides playbook and inventory host and group variables, so this can lead to unexpected side effects.

The following table shows some examples of facts with both the ansible_* and ansible_facts.* names.

Table 3.4. Comparison of Selected Ansible Fact Names

ansible_facts.* nameansible_* name
ansible_facts['hostname'] ansible_hostname
ansible_facts['fqdn'] ansible_fqdn
ansible_facts['default_ipv4']['address'] ansible_default_ipv4['address']
ansible_facts['interfaces'] ansible_interfaces
ansible_facts['devices']['vda']['partitions']['vda1']['size'] ansible_devices['vda']['partitions']['vda1']['size']
ansible_facts['dns']['nameservers'] ansible_dns['nameservers']
ansible_facts['kernel'] ansible_kernel

Important

Currently, Ansible recognizes both the new fact-naming system (using ansible_facts) and the earlier, pre-2.5 "facts injected as separate variables" naming system.

You can disable the ansible_ naming system by setting the inject_facts_as_vars parameter in the [defaults] section of the Ansible configuration file to false. The default setting is currently true.

If it is set to false, you can only reference Ansible facts using the new ansible_facts.* naming system. In that case, attempts to reference facts through the ansible_* namespace results in an error.

Turning off Fact Gathering

Sometimes, you do not want to gather facts for your play. This might be for several reasons:

  • You might not be using any facts and want to speed up the play, or reduce load caused by the play on the managed hosts.

  • The managed hosts perhaps cannot run the ansible.builtin.setup module for some reason, or you need to install some prerequisite software before gathering facts.

To disable fact gathering for a play, set the gather_facts keyword to no:

---
- name: This play does not automatically gather any facts
  hosts: large_datacenter
  gather_facts: no

Even if gather_facts: no is set for a play, you can manually gather facts at any time by running a task that uses the ansible.builtin.setup module:

  tasks:
    - name: Manually gather facts
      ansible.builtin.setup:

Gathering a Subset of Facts

All facts are gathered by default. You can configure the ansible.builtin.setup module to only gather a subset of facts, instead of all facts. For example, to only gather hardware facts, set gather_subset to hardware:

- name: Collect only hardware facts
  ansible.builtin.setup:
    gather_subset:
      - hardware

If you want to gather all facts except a certain subset, add an exclamation point (!) in front of the subset name:

- name: Collect all facts except for hardware facts
  ansible.builtin.setup:
    gather_subset:
      - !hardware

Visit https://docs.ansible.com/ansible/latest/collections/ansible/builtin/setup_module.html#parameter-gather_subset to view possible values for the gather_subset parameter.

Creating Custom Facts

You can use custom facts to define certain values for managed hosts. Plays can use custom facts to populate configuration files or conditionally run tasks.

Custom facts are stored locally on each managed host. These facts are integrated into the list of standard facts gathered by the ansible.builtin.setup module when it runs on the managed host.

You can statically define custom facts in an INI or JSON file, or you can generate them dynamically when you run a play. Dynamic custom facts are gathered via executable scripts, which generate JSON output.

By default, the ansible.builtin.setup module loads custom facts from files and scripts in the etc/ansible/facts.d directory of each managed host. The name of each file or script must end in .fact for it to be used. Dynamic custom fact scripts must output JSON-formatted facts and must be executable.

The following example static custom facts file is written in INI format. An INI-formatted custom facts file contains a top level defined by a section, followed by the key-value pairs of the facts to define:

[packages]
web_package = httpd
db_package = mariadb-server

[users]
user1 = joe
user2 = jane

You can provide the same facts in JSON format. The following JSON facts are equivalent to the facts specified by the INI format in the preceding example. The JSON data could be stored in a static text file or printed to standard output by an executable script:

{
  "packages": {
    "web_package": "httpd",
    "db_package": "mariadb-server"
  },
  "users": {
    "user1": "joe",
    "user2": "jane"
  }
}

Note

Custom fact files cannot be in YAML format like a playbook. JSON format is the closest equivalent.

The ansible.builtin.setup module stores custom facts in the ansible_facts['ansible_local'] variable. Facts are organized based on the name of the file that defined them. For example, assume that the /etc/ansible/facts.d/custom.fact file on the managed host produces the preceding custom facts. In that case, the value of ansible_facts['ansible_local']['custom']['users']['user1'] is joe.

You can inspect the structure of your custom facts by gathering facts and using the ansible.builtin.debug module to display the contents of the ansible_local variable with a play similar to the following example:

- name: Custom fact testing
  hosts: demo1.example.com
  gather_facts: yes

  tasks:
    - name: Display all facts in ansible_local
      ansible.builtin.debug:
        var: ansible_local

When you run the play, you might see output similar to the following example:

...output omitted...
TASK [Display all facts in ansible_local] *********************************
ok: [demo1.example.com] => {
    "ansible_local": {
        "custom": {
            "packages": {
                "db_package": "mariadb-server",
                "web_package": "httpd"
            },
            "users": {
                "user1": "joe",
                "user2": "jane"
            }
        }
    }
}
...output omitted...

You can use custom facts the same way as default facts in playbooks:

[user@demo ~]$ cat playbook.yml
---
- hosts: all
  tasks:
  - name: Prints various Ansible facts
    ansible.builtin.debug:
      msg: >
           The package to install on {{ ansible_facts['fqdn'] }}
           is {{ ansible_facts['ansible_local']['custom']['packages']['web_package'] }}

[user@demo ~]$ ansible-navigator run -m stdout playbook.yml
PLAY [all] ***********************************************************************

TASK [Gathering Facts] *****************************************************
ok: [demo1.example.com]

TASK [Prints various Ansible facts] ****************************************
ok: [demo1.example.com] => {
    "msg": "The package to install on demo1.example.com  is httpd"
}

PLAY RECAP *****************************************************************
demo1.example.com    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Using Magic Variables

Ansible sets some special variables automatically.

These magic variables can also be useful to get information specific to a particular managed host.

Magic variable names are reserved, so you should not define variables with these names.

Four of the most useful magic variables are:

hostvars

Contains the variables for managed hosts, and can be used to get the values for another managed host's variables. It does not include the managed host's facts if they have not yet been gathered for that host.

group_names

Lists all groups that the current managed host is in.

groups

Lists all groups and hosts in the inventory.

inventory_hostname

Contains the hostname for the current managed host as configured in the inventory. This might be different from the hostname reported by facts for various reasons.

One way to get insight into their values is to use the ansible.builtin.debug module to display the contents of these variables.

For example, the following task causes every host that runs the play to print out a list of all network interfaces on the demo2.example.com host. This task works as long as facts were gathered for demo2 earlier in the play or by a preceding play in the playbook. It uses the hostvars magic variable to access the ansible_facts['interfaces'] fact for that host.

    - name: Print list of network interfaces for demo2
      ansible.builtin.debug:
        var: hostvars['demo2.example.com']['ansible_facts']['interfaces']

You can use the same approach with regular variables, not only facts. Keep in mind that the preceding task is run by every host in the play, so it would be more efficient to use a different module to apply information gathered from one host to the configuration of each of those other managed hosts.

Remember that you can use the ansible.builtin.setup module in a task to refresh gathered facts at any time. However, fact gathering does cause your playbook to take longer to run.

Several other magic variables are also available. For more information, see https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html.

Revision: rh294-9.0-c95c7de