Skip to main content

No wizardry needed to use Ansible's magic variable 'hostvars'

Get past the illusion of hostvars being difficult and begin using them in your playbooks, tasks, and roles.
Image
Magic trick with Ace of Spades

Image by Pexels from Pixabay

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. ]

Topics:   Ansible   Automation   Programming  

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

Try Red Hat Enterprise Linux

Download it at no charge from the Red Hat Developer program.