Search code examples
ansiblejinja2

Look Up List of Hostvars for Each Host in Inventory


I am trying to find a way of creating the following result with the Ansible debug module.

Desired Results

{
  "myserver01": {
    "host_var_one": "foo",
    "host_var_two": "bar"
  },
  "myserver02": {
    "host_var_one": "biz",
    "host_var_two": "baz"
  },
  "myserver03": {
    "host_var_one": "fizz",
    "host_var_two": "buzz"
  }
}

Example Inventory

[my_servers]
myserver01 host_var_one=foo host_var_two=bar
myserver02 host_var_one=biz host_var_two=baz
myserver03 host_var_one=fizz host_var_two=buzz

I would like to be able to provide a list of hostvars and have them displayed in a dict under each host in the inventory, where the key is the hostvar name, and the value is the hostvar value. Including another hostvar in the results should ideally just require adding another variable name to a list.

For example, in the task I would list that I want ["host_var_one", "host_var_two"] for each host in the inventory and get the above desired results.

I have the following task that gets somewhat close to what I want. I just can't figure out a way of listing all desired variables that I want for each host in the format described above. The following only works with one variable, and it doesn't list the variable name along with the value.

myplaybook.yml

---
- name: Test
  hosts: all
  gather_facts: yes
  user: ansible
  become: yes

  tasks:
    - name: Debug
      debug:
        msg: "{{ dict(query('inventory_hostnames', 'all') | zip(query('inventory_hostnames', 'all') | map('extract', hostvars, 'host_var_one'))) }}"
      run_once: yes

Results of myplaybook.yml

$ ansible-playbook -i inventory test.yml --limit "myserver01"
PLAY [Test] **************************************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [myserver01]

TASK [Debug] *************************************************************************************************************************************************
ok: [myserver01] => {
    "msg": {
        "myserver01": "foo",
        "myserver02": "biz",
        "myserver03": "fizz"
    }
}

Solution

  • My solution is as follows:

    - name: Dict of host_vars
      debug:
        msg: "{{ dict( keys | zip(values) ) }}"
      vars:
        keys: "{{ hostvars | dict2items | map(attribute='key') }}"
        values: "{{ hostvars | dict2items | map(attribute='value') | map('dict2items')
          | map('selectattr', 'key', 'match', 'host_var_') | map('items2dict') }}"
      run_once: yes
    

    The result looks like:

    TASK [Host vars dict] **************************************************
    ok: [myserver01] => {
        "msg": {
            "myserver01": {
                "host_var_one": "foo",
                "host_var_two": "bar"
            },
            "myserver02": {
                "host_var_one": "biz",
                "host_var_two": "baz"
            },
            "myserver03": {
                "host_var_one": "fizz",
                "host_var_two": "buzz"
            }
        }
    }
    

    Step by step explanation

    Two separate lists are created: keys and values.

    The list for the keys is created by converting the dict with dict2items, so that the attribute key can be extracted by map. This way you get the list:

    [ "myserver01", "myserver02", "myserver03" ]
    

    For the values it works the same way, that a list of the value attributes is generated. These lists are in each case dicts and must be converted therefore by map('dict2items') again into a list. Now the key can be filtered over the list via selectattr. Here host_var_ is filtered out by match (the beginning of a string). Afterwards the result (list in list) must be converted by map('items2dict') back into a dict.

    [
      {
        "host_var_one": "foo",
        "host_var_two": "bar"
      },
      {
        "host_var_one": "biz",
        "host_var_two": "baz"
      },
      {
        "host_var_one": "fizz",
        "host_var_two": "buzz"
      }
    ]
    

    Finally, the two lists are merged via zip and converted back into one via dict.

    Variables filtered by prefix

    The variables are filtered by the prefix of their name, so several different ones (with the same prefix) can be defined for different hosts and will be filtered out completely.

    E.g. the Inventory:

    [my_servers]
    myserver01 host_var_one=foo host_var_two=bar
    myserver02 host_var_one=biz host_var_two=baz host_var_three=boz host_var_four=buz
    myserver03 host_var_one=fizz host_var_two=buzz host_var_six=dazz
    

    returns the following result:

    TASK [Dict of host_vars] **********************************************
    ok: [myserver01] => {
        "msg": {
            "myserver01": {
                "host_var_one": "foo",
                "host_var_two": "bar"
            },
            "myserver02": {
                "host_var_four": "buz",
                "host_var_one": "biz",
                "host_var_three": "boz",
                "host_var_two": "baz"
            },
            "myserver03": {
                "host_var_one": "fizz",
                "host_var_six": "dazz",
                "host_var_two": "buzz"
            }
        }
    }