Reputation: 845
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
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
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
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"
}
}
}
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
.
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
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