Andrew Paglusch
Andrew Paglusch

Reputation: 845

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"
    }
}

Upvotes: 2

Views: 3305

Answers (4)

Eos Antigen
Eos Antigen

Reputation: 388

There is a built-in command for this :

ansible-inventory --list -i inventory/hosts.ini

It outputs the entire tree of your inventory along with the vars assigned.

Also, in the same directory level as the inventory file, you can have a folder named group_vars (this is also a built-in pre-defined naming convention) , which will keep all your vars per host or per host group... Read more in the manual here .

Upvotes: 0

Vladimir Botka
Vladimir Botka

Reputation: 68189

The simplest solution is putting the below declarations into the vars

selection: "{{ dict(ansible_play_hosts_all|
                    zip(hostvars|json_query(_query))) }}"
_query: '*.{host_var_one: host_var_one, host_var_two: host_var_two}'

Given the above inventory, the result is

  selection:
    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

In the dictionary, all hosts keep the same variables selected by the common _query.


Example of a complete playbook for testing

- hosts: my_servers
  gather_facts: false

  vars:

    selection: "{{ dict(ansible_play_hosts_all|
                        zip(hostvars|json_query(_query))) }}"
    _query: '*.{host_var_one: host_var_one, host_var_two: host_var_two}'

  tasks:

    - debug:
        var: selection
      run_once: true

Q: "List the variables for each host in the inventory."

A: For example, given the inventory

shell> cat hosts
all:
  children:
    my_servers:
      hosts:
        myserver01:
          host_var_one: foo
          host_var_two: bar
          host_var_three: baz
          sel_vars: [host_var_one, host_var_two, host_var_three]
        myserver02:
          host_var_one: biz
          host_var_two: baz
          sel_vars: [host_var_one, host_var_two]
        myserver03:
          host_var_one: fiz
          host_var_two: buz
          sel_vars: [host_var_two]

The playbook below

- hosts: my_servers
  gather_facts: false

  vars:

    sel_vals: "{{ sel_vars|map('extract', vars)|list }}"
    sel_dict: "{{ dict(sel_vars|zip(sel_vals)) }}"
    sel_all: "{{ ansible_play_hosts_all|map('extract', hostvars, 'sel_dict') }}"
    selection: "{{ dict(ansible_play_hosts_all|zip(sel_all)) }}"

  tasks:

    - set_fact:
        sel_dict: "{{ sel_dict }}"
    - debug:
        var: selection
      run_once: true

gives (abridged)

  selection:
    myserver01:
      host_var_one: foo
      host_var_three: baz
      host_var_two: bar
    myserver02:
      host_var_one: biz
      host_var_two: baz
    myserver03:
      host_var_two: buz

The task set_fact is necessary to "instantiate" (create in hostvars) the variables sel_dict for all hosts.

You can select the variables dynamically. For example, put the below declaration into the vars

    sel_vars: "{{ query('varnames', '.*_one') }}"

The result will be

  selection:
    myserver01:
      host_var_one: foo
    myserver02:
      host_var_one: biz
    myserver03:
      host_var_one: fiz

Upvotes: 3

phanaz
phanaz

Reputation: 1632

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"
        }
    }
}

Upvotes: 5

mdaniel
mdaniel

Reputation: 33231

I always enjoy seeing the competing ways of solving these problems, but here's mine: because json_query is rotten at dealing with top-level dicts, a little sprinkling of dict2items rotates it into an array of dicts which JMESPath is much better at dealing with, project the items into the same [{key: .key, value: ...}] shape for the things you want, then rotate it back into a dict with the opposite filter (items2dict)

  - debug:
      msg: '{{ hostvars | dict2items | json_query(jq) | items2dict }}'
    delegate_to: localhost
    run_once: true
    vars:
      jq: '[*].{key: key, value: {one: value.host_var_one, two: value.host_var_two}}'

produces

ok: [alpha -> localhost] => {
    "msg": {
        "alpha": {
            "one": "a1",
            "two": "a2"
        },
        "beta": {
            "one": "b1",
            "two": "b2"
        }
    }
}

I didn't follow what you were doing with the zip but to the best of my knowledge hostvars contains all hostvars, and thus no zip trickery required


Or, if you have either a huge list, or an unknown list, of those hostvars to extract, I gravely lament the lack of dict comprehensions in ansible's Jinja2 but the iterative version works just fine:

  - debug:
      msg: >-
        {%- set r = {} -%}
        {%- for hv in hostvars.keys()  -%}
        {%-   set d = {} -%}
        {%-   for f in the_facts -%}
        {%-     set _ = d.update({f: hostvars[hv][f]}) -%}
        {%-   endfor -%}
        {%-   set _ = r.update({hv: d}) -%}
        {%- endfor -%}
        {{ r }}
    vars:
      the_facts:
      - host_var_one
      - host_var_two

Upvotes: 2

Related Questions