Ansible: Working with Variables and Hostvars

When I started to write about my infrastructure@home project, Ansible became the central configuration management tool. In order to be more effective with Ansible, I started to read the books Ansible Up and Running and Mastering Ansible. I learned a lot more about Ansible then what is necessary for the project, but those things are interesting in itself and I want to share them in a tutorial style. This is the first article.

In this article, we will learn what host facts are and how to see them, and we will now learn how to define variables from task execution.

This article originally appeared at my blog.

Gather and Display Host Fact

- name: Retrieve host vars
hosts:
- raspis
- server
tasks:
- debug:
var=hostvars[inventory_hostname]

We will execute this playbook only for the node raspi-4-1.

ansible-playbook retrieve_host_vars —limit "raspi-4-1"TASK [get variables] **********************************************************************************
ok: [raspi-4-1] => {
"hostvars[inventory_hostname]": {
"_facts_gathered": true,
"all_ipv4_addresses": [
"192.168.2.111"
],
"architecture": "armv7l",
"distribution": "Debian",
"distribution_release": "buster",
"distribution_version": "10",
"hostname": "raspi-4-1",
"hostnqn": "",
"kernel": "4.19.97-v7l+",
"kernel_version": "#1294 SMP Thu Jan 30 13:21:14 GMT 2020",
[...]
"machine": "armv7l",
"machine_id": "edaca707ad4b4b8991d2341c902160f5",
"memfree_mb": 3670,
"memtotal_mb": 3906,
"nodename": "raspi-4-1",
"os_family": "Debian",
"pkg_mgr": "apt",
"processor_cores": 1,
"processor_count": 4,
"processor_threads_per_core": 1,
"processor_vcpus": 4,
"product_name": "",
"product_serial": "",
"product_uuid": "",
"product_version": "",
"service_mgr": "systemd",
"uptime_seconds": 8359
}
}

The output only shows an excerpt of the variables. As you can see, Ansible retrieves facts about the operating system, the hardware architecture, memory and cpu, and IP addresses. There are interesting use cases to access and use this information, for example you can collect the ip addresses of the hosts and use them to create a Nginx config file.

You can also execute this command as an ad-hoc task:

ansible -i hosts all -m debug -a "var=hostvars[inventory_hostname]"

Once you are familiar with the facts, you can quickly query a node by using the following command:

ansible -i hosts all -m setup -a "filter=architecture"

Define and use variables

- name: Retrieve hostname
hosts:
- raspis
- server
tasks:
- name: Retrieve the hostname
command: hostname

When we execute this playbook, we can see the following output:

ansible-playbook playbook/retrieve_hostname.yml --limit raspisPLAY [Retrieve and show hostname of nodes] *****************************************************TASK [Gathering Facts] ********************************************************************************
ok: [raspi-4-2]
ok: [raspi-4-1]
ok: [raspi-3-2]
ok: [raspi-3-1]
TASK [Retrieve the hostname] **************************************************************************
changed: [raspi-4-1]
changed: [raspi-4-2]
changed: [raspi-3-1]
changed: [raspi-3-2]
PLAY RECAP ********************************************************************************************
raspi-3-1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
raspi-3-2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
raspi-4-1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
raspi-4-2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

The command works, but we did not see any output. How can we store the return value of the command? We need to save the result of the hostname command in a variable, and then we need to print this variable to the console:

- name: Retrieve hostname
hosts:
- raspis
- server
tasks:
- name: Retrieve the hostname
command: hostname
register: result
- debug:
var: hostname

Let’s execute this playbook:

TASK [debug] ******************************************************************************************
ok: [raspi-3-1] => {
"result": {
"changed": true,
"cmd": [
"hostname"
],
"delta": "0:00:00.006215",
"end": "2020-02-23 10:35:52.757907",
"failed": false,
"rc": 0,
"start": "2020-02-23 10:35:52.751692",
"stderr": "",
"stderr_lines": [],
"stdout": "raspi-3-1",
"stdout_lines": [
"raspi-3-1"
]
}
}

What happens here? We see a lot more than expected! Each Ansible module returns a data structure that includes information like time, return code, task failed and more. However, we are specifically interested into the value stdout. Also, we want to store this variable as a fact of the host with the set_fact module. Then, this variable will be available in all subsequent tasks throughout the playbook.

Here is this version:

- name: Retrieve hostname
hosts:
- raspis
- server
tasks:
- name: Retrieve the hostname
command: hostname
register: result
- set_fact:
hostname: {{ result.stdout }}
- debug:
var: hostname

Let’s see the output of this playbook:

TASK [debug] ******************************************************************************************
ok: [raspi-3-1] => {
"hostname": "raspi-3-1"
}
ok: [raspi-3-2] => {
"hostname": "raspi-3-2"
}
ok: [raspi-4-1] => {
"hostname": "raspi-4-1"
}
ok: [raspi-4-2] => {
"hostname": "raspi-4-2"
}

Exactly what we wanted.

Conclusion

IT Project Manager & Developer