No wizardry needed to use Ansible's magic variable 'hostvars'
First, some basic definitions and rules: In Ansible, there are a set of reserved variables called magic variables. Because they are reserved, they cannot be set by users and, if tried, Ansible will override them.
This article focuses one magic variable in particular: hostvars
, which can access variables defined for any host in a play at any point in a playbook run.
Most Ansible users know the inventory may contain additional variables that are assigned to a specific host. Sometimes, these variables associated with a host may be needed by other hosts during a playbook run. In this article, I'll expand your understanding of using hostvars
outside of the linear host variable-to-host relationship.
[ Compare Ansible vs. Red Hat Ansible Automation Platform ]
Create a simple inventory
First, I'll create a simple ini
type inventory based on some generic role-playing character types. For simplicity, I will avoid using groups
or group_vars
. I'll also avoid spinning up any additional hosts for this exercise by using the loopback for the ansible_host
variable.
inventory.ini
servera ansible_host=127.0.0.1 character_type=Bard spell1=Blindness spell2=Confusion
serverb ansible_host=127.0.0.1 character_type=Wizard spell1=Lightning spell2=Fireball
serverc ansible_host=127.0.0.1 character_type=Druid spell1=Poison spell2=Plague
serverd ansible_host=127.0.0.1 character_type=Paladin spell1="Magic Missile" spell2=Fear
Each host is assigned the following variables: ansible_host
, character_type
, spell1
, and spell2
. Each value, with the exception of ansible_host
, is unique to the host in the inventory.
Display hostvars
I'll create a playbook to showcase the hostvars
available for use. I'll also skip gathering facts to streamline the playbook.
example1.yml
---
- name: Example playbook to showcase hostvars
hosts: all
connection: local
gather_facts: false
tasks:
- name: Display the specific hostvars that are set in the inventory for each host
ansible.builtin.debug:
var: hostvars[inventory_hostname]|json_query('[character_type,spell1,spell2]')
NOTE: You must install the community.general collection to use the json_query filter.
Now, I'll run the first playbook:
$ ansible-playbook -i inventory.ini example1.yml
example1.yml results
PLAY [Example playbook to showcase hostvars] ***********************************
TASK [Display all of the hostvars for each host] *******************************
Sunday 09 April 2023 13:09:46 -0400 (0:00:00.018) 0:00:00.018 **********
ok: [servera] => {
"hostvars[inventory_hostname]|json_query('[character_type,spell1,spell2]')": [
"Bard",
"Blindness",
"Confusion"
]
}
ok: [serverb] => {
"hostvars[inventory_hostname]|json_query('[character_type,spell1,spell2]')": [
"Wizard",
"Lightning",
"Fireball"
]
}
ok: [serverc] => {
"hostvars[inventory_hostname]|json_query('[character_type,spell1,spell2]')": [
"Druid",
"Poison",
"Plague"
]
}
ok: [serverd] => {
"hostvars[inventory_hostname]|json_query('[character_type,spell1,spell2]')": [
"Paladin",
"Magic Missile",
"Fear"
]
}
PLAY RECAP *********************************************************************
servera : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverb : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverc : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverd : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
As you can see above, the playbook extracted the unique hostvars
as it ran against each host. This is a typical use of hostvars
.
[ Write your first Ansible playbook in this hands-on interactive lab. ]
What if I need to use hostvars from a different host?
Sometimes, a single host may need a variable from another host. I'll tackle that in playbook number 2.
example2.yml
---
- name: Example playbook to showcase hostvars
hosts: all
gather_facts: false
connection: local
tasks:
- name: Set a fact for servera using serverd host variable
ansible.builtin.set_fact:
opponent: "{{ hostvars['serverd']['character_type'] }}"
when: inventory_hostname == "servera"
- name: Print servera hostvars setting derived from a variable from serverd
ansible.builtin.debug:
var: hostvars[inventory_hostname]['opponent']
when:
- opponent is defined
- name: Print PvP message
ansible.builtin.debug:
msg:
- "Round1: {{ character_type }} vs {{ opponent }}"
when: opponent is defined
$ ansible-playbook -i inventory.ini example2.yml
example2.yml results
PLAY [Example playbook to showcase hostvars] ***********************************
TASK [Set a fact from serverd to use for servera] ******************************
Sunday 09 April 2023 14:34:19 -0400 (0:00:00.029) 0:00:00.030 **********
skipping: [serverb]
ok: [servera]
skipping: [serverc]
skipping: [serverd]
TASK [Print servera hostvars setting derived from a variable from serverd] ***************
Sunday 09 April 2023 14:34:20 -0400 (0:00:00.061) 0:00:00.091 **********
skipping: [serverb]
ok: [servera] => {
"hostvars[inventory_hostname]['opponent']": "Paladin"
}
skipping: [serverc]
skipping: [serverd]
TASK [Print PvP message] *******************************************************
Sunday 09 April 2023 14:34:20 -0400 (0:00:00.079) 0:00:00.170 **********
ok: [servera] => {
"msg": [
"Round1: Bard vs Paladin"
]
}
skipping: [serverb]
skipping: [serverc]
skipping: [serverd]
PLAY RECAP *********************************************************************
servera : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverb : ok=0 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
serverc : ok=0 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
serverd : ok=0 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
Well, that's cool! Using the character_type
variable from serverd
, the playbook could assign it as a variable for servera
to use.
What if I need to use all specific hostvars from each host on all the hosts?
Sometimes you need to dynamically get the desired host variables from all hosts and use them on all servers. I'll create playbook 3 to show this.
example3.yml
---
- name: Example playbook to showcase hostvars
hosts: all
gather_facts: false
connection: local
tasks:
- name: Set a fact called all_character_types
ansible.builtin.set_fact:
all_character_types: "{{ all_character_types|default([]) + [ hostvars[item]['character_type'] ] }}"
loop: "{{ groups['all'] }}"
run_once: true
- name: Print hostvars that shows servera has all character_types
ansible.builtin.debug:
var: all_character_types
when: inventory_hostname == "servera"
- name: Print hostvars that shows serverb has all character_types
ansible.builtin.debug:
var: all_character_types
when: inventory_hostname == "serverb"
- name: Print hostvars that shows serverc has all character_types
ansible.builtin.debug:
var: all_character_types
when: inventory_hostname == "serverc"
- name: Print hostvars that shows serverd has all character_types
ansible.builtin.debug:
var: all_character_types
when: inventory_hostname == "serverd"
Now I'll run playbook 3:
$ ansible-playbook -i inventory.ini example3.yml
example3.yml results
PLAY [Example playbook to showcase hostvars] ***********************************
TASK [Set a fact called all_character_types] ******************************************
Sunday 09 April 2023 14:59:33 -0400 (0:00:00.030) 0:00:00.030 **********
ok: [servera] => (item=servera)
ok: [servera] => (item=serverb)
ok: [servera] => (item=serverc)
ok: [servera] => (item=serverd)
TASK [Print hostvars that shows servera has all character_types] *********************
Sunday 09 April 2023 14:59:33 -0400 (0:00:00.080) 0:00:00.111 **********
skipping: [serverb]
ok: [servera] => {
"all_character_types": [
"Bard",
"Wizard",
"Druid",
"Paladin"
]
}
skipping: [serverc]
skipping: [serverd]
TASK [Print hostvars that shows serverb has all character_types] *********************
Sunday 09 April 2023 14:59:33 -0400 (0:00:00.056) 0:00:00.167 **********
skipping: [servera]
skipping: [serverc]
ok: [serverb] => {
"all_character_types": [
"Bard",
"Wizard",
"Druid",
"Paladin"
]
}
skipping: [serverd]
TASK [Print hostvars that shows serverc has all character_types] *********************
Sunday 09 April 2023 14:59:33 -0400 (0:00:00.054) 0:00:00.221 **********
skipping: [servera]
skipping: [serverb]
ok: [serverc] => {
"all_character_types": [
"Bard",
"Wizard",
"Druid",
"Paladin"
]
}
skipping: [serverd]
TASK [Print hostvars that shows serverd has all character_types] *********************
Sunday 09 April 2023 14:59:33 -0400 (0:00:00.053) 0:00:00.275 **********
skipping: [servera]
skipping: [serverb]
skipping: [serverc]
ok: [serverd] => {
"all_character_types": [
"Bard",
"Wizard",
"Druid",
"Paladin"
]
}
PLAY RECAP *********************************************************************
servera : ok=2 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
serverb : ok=1 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
serverc : ok=1 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
serverd : ok=1 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
In the above output, you can see that the playbook sets a fact called all_character_types
. You can also see that each host now has that fact available to it. By creating a fact based on the hostvars
of each host, you now have a list of all the character_types
. You can use this fact later.
Spells battle
I'll create a playbook matching character_types
and a random spell for each.
I'll loop through all of the hosts in the inventory and leverage the hostvars
of each during the loop. I'll also supplement the playbook to use the following Jinja filters:
example4.yml
---
- name: Example playbook to showcase hostvars
hosts: all
gather_facts: false
connection: local
tasks:
- name: Select a spell to be used against another opponent
ansible.builtin.debug:
msg:
- "{{ character_type }} uses his {{ [spell1,spell2]|random }} spell against {{ hostvars[item]['character_type'] }}"
- "{{ hostvars[item]['character_type'] }} uses {{ hostvars[item][('spell1','spell2')|random] }} spell against {{ character_type }}"
loop: "{{ groups['all']|reject('search',inventory_hostname)|sort|list }}"
Now I'll run playbook 4:
$ ansible-playbook -i inventory.ini example4.yml
example4.yml results
PLAY [Example playbook to showcase hostvars] ***********************************
TASK [Select a spell to be used against another opponent] *******************
Sunday 09 April 2023 17:48:38 -0400 (0:00:00.030) 0:00:00.030 **********
ok: [servera] => (item=serverb) => {
"msg": [
"Bard uses his Blindness spell against Wizard",
"Wizard uses Fireball spell against Bard"
]
}
ok: [serverb] => (item=servera) => {
"msg": [
"Wizard uses his Fireball spell against Bard",
"Bard uses Blindness spell against Wizard"
]
}
ok: [serverc] => (item=servera) => {
"msg": [
"Druid uses his Plague spell against Bard",
"Bard uses Confusion spell against Druid"
]
}
ok: [servera] => (item=serverc) => {
"msg": [
"Bard uses his Confusion spell against Druid",
"Druid uses Poison spell against Bard"
]
}
ok: [serverd] => (item=servera) => {
"msg": [
"Paladin uses his Fear spell against Bard",
"Bard uses Confusion spell against Paladin"
]
}
ok: [serverb] => (item=serverc) => {
"msg": [
"Wizard uses his Lightning spell against Druid",
"Druid uses Poison spell against Wizard"
]
}
ok: [serverc] => (item=serverb) => {
"msg": [
"Druid uses his Poison spell against Wizard",
"Wizard uses Lightning spell against Druid"
]
}
ok: [servera] => (item=serverd) => {
"msg": [
"Bard uses his Confusion spell against Paladin",
"Paladin uses Fear spell against Bard"
]
}
ok: [serverb] => (item=serverd) => {
"msg": [
"Wizard uses his Fireball spell against Paladin",
"Paladin uses Fear spell against Wizard"
]
}
ok: [serverd] => (item=serverb) => {
"msg": [
"Paladin uses his Fear spell against Wizard",
"Wizard uses Lightning spell against Paladin"
]
}
ok: [serverc] => (item=serverd) => {
"msg": [
"Druid uses his Poison spell against Paladin",
"Paladin uses Magic Missile spell against Druid"
]
}
ok: [serverd] => (item=serverc) => {
"msg": [
"Paladin uses his Magic Missile spell against Druid",
"Druid uses Plague against spell Paladin"
]
}
PLAY RECAP *********************************************************************
servera : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverb : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverc : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
serverd : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
In the final example, I'll do a simple extraction of variables from a group using map
and json_query
against the hostvars
. While this is very similar to the first example, I'm actually extracting the hostvars
using the Jinja map filter. I then pass that information to json_query
and grab 2 specific bits of information
NOTE: The json_query
filter requires that you install the community.general
collection.
example5.yml
---
- name: Example playbook to showcase hostvars
hosts: all
gather_facts: false
connection: local
tasks:
- name: Print specific hostvars from all groups
ansible.builtin.debug:
var: groups['all'] | map('extract',hostvars)|json_query('[].[character_type,spell2]')
delegate_to: localhost
become: false
run_once: true
Now I'll run playbook 5:
$ ansible-playbook -i inventory.ini example5.yml
example5.yml results
PLAY [Example playbook to showcase hostvars] ***********************************
TASK [Print specific hostvars from all groups ] ******************
ok: [servera -> localhost] => {
"groups['all'] | map('extract',hostvars)|json_query('[].[character_type,spell2]')": [
[
"Bard",
"Confusion"
],
[
"Wizard",
"Fireball"
],
[
"Druid",
"Plague"
],
[
"Paladin",
"Fear"
]
]
}
PLAY RECAP *********************************************************************
servera : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Wrap up
Personally, it took me a while before I began using the hostvars
magic variables in the manner I have shown in this article. However, you don't have to be a magician to use hostvars
because the magic is in the examples above. Get past the illusion of hostvars
being difficult and begin using them in your playbooks, tasks, and roles. You'll be amazed at how easy it is and how much your code will improve.
[ Need more on Ansible? Take a no-cost technical overview course from Red Hat. Ansible Essentials: Simplicity in Automation Technical Overview. ]
Randy Romero
Randy Romero is a Senior Ansible Consultant and Red Hat Certified Architect at Red Hat where he specializes in IT automation with Ansible and Ansible Automation Platform. He has experience in the cable, insurance, and loyalty-marketing industries, having performed multiple roles starting with ju More about me