How to get variables from all hosts in Ansible? - ansible

I want to get an array of values of the specific variable from all hosts. For example: let's say I have host dc1.com with variable test_var = "value1" and host dc2.com with variable test_var = "value2". I want to get an array of these values that looks like this [ "value1", "value2" ]. Is it possible?

You can fetch any existing var from any available host through the hostvars dict availab as a magic var
e.g.:
hostvars["dc1.com"].test_var
If you want a list of all found values, the following will got through all hosts in your inventory and extract the defined values in a list. Check the documentation on filters for more details and to arrange to your exact requirements.
- name: Show a list of all test_var values in my inventory
debug:
msg: "{{ hostvars | dict2items | selectattr('value.test_var', 'defined') | map(attribute='value.test_var') }}"
Bonus (once more study the documentation above for more explanation), almost the same as above but only for your two example hosts. Note I dropped the attribute defined filter taking for granted the var will exist.
- name: Show list of test_var valuse for dc1 and dc2 hosts
vars:
host_list:
- dc1.com
- dc2.com
debug:
msg: "{{ host_lists | map('extract', hostvars) | map(attribute='test_var') }}"

Related

How do I format each string in a list of strings in ansible

I'm trying to write a json file with a list of ip addresses formatted as "http://{ip_address}:{port}" as a part of an ansible playbook. I'm able to get `"{ip_address}:port" with the following code:
vars:
config:
addresses: "{{ groups['other']
| map('extract', hostvars)
| map(attribute='ansible_host')
| product([':' ~ other_port]) | map('join')
}}"
I tried using the format filter but it expects the input to be the format pattern not a variable to insert into a format.
If I was allowed to execute arbitrary python would express what I want as:
vars:
config:
addresses: "{{ [ f'http://{host.hostvars.ansible_host}:{port}' for host in groups['other'] }}"
but it's all got to be in jinja2 filters. I feel like being able to even tack on the port number was a fluke. Is there a way to express this in jinja? Right now I'm looking at hard-coding the format into the hosts file.
For example, given the inventory
shell> cat hosts
[other]
test_11 ansible_host=10.1.0.61
test_12 ansible_host=10.1.0.62
test_13 ansible_host=10.1.0.63
The playbook below
- hosts: localhost
vars:
other_port: 80
addresses: "{{ groups.other|
map('extract', hostvars, 'ansible_host')|
map('regex_replace', '^(.*)$', addresses_replace)|
list }}"
addresses_replace: 'http://\1:{{ other_port }}'
tasks:
- debug:
var: addresses
gives (abridged)
addresses:
- http://10.1.0.61:80
- http://10.1.0.62:80
- http://10.1.0.63:80

How can I resolve an inventory member who has a certain attribute?

I need to pass a specific host from my inventory as a parameter into a role. The host is part of a group but is demarcated by a variable that none of the other hosts have.
snippet of: hosts.yml
dbservers:
hosts:
pg01:
ansible_host: pg01.domain.com
master_slave: master
pg02:
ansible_host: pg02.domain.com
master_slave: slave
I want to be able to resolve pg01 based on the fact that the variable master_slave is set to 'master' such that I can call into a role like this:
- name: Do something
include_role:
name: a.database.role.to.run.on.master
vars:
master_database_host: {{ something that resolves to pg01 }}
How can I resolve the appropriate host from inventory?
You can use a mix of filters to extract the host you need:
tasks:
- debug:
msg: '{{groups["group_name"] | map("extract", hostvars) | selectattr("master_slave", "equalto", "master") | map(attribute="inventory_hostname") | list}}'
Step by step:
groups["group_name"] is a list of all the hosts in the group group_name.
map("extract", hostvars) takes hostvars, a dictionary mapping the host to their variables, and extracts the hosts that are in group_name (i.e. groups["group_name"]). This results in a list containing the hosts in group_name mapped to their variables.
selectattr("master_slave", "equalto", "master") selects all hosts who have an attribute master_slave that equals to master. This result in a list with all the hosts that are masters mapped to their variables.
map(attribute="inventory_hostname") takes a list as input and returns the inventory_hostname attribute of every item. This result in a list with all the hosts that are masters.
The play below (with json_query)
- hosts: dbservers
tasks:
- set_fact:
master_database_host: "{{ groups['dbservers']|
map('extract',hostvars)|
list|
json_query('[?master_slave==`master`].inventory_hostname')|
first }}"
- debug:
var: master_database_host
gives
ok: [pg02] => {
"master_database_host": "pg01"
}
ok: [pg01] => {
"master_database_host": "pg01"
}
You can use if else condition in vars to assign the values.
So your play should be Something like this.
- name: Do something
include_role:
name: a.database.role.to.run.on.master
vars:
master_database_host: "{{ hostvars['pg01']['ansible_host'] if \"{{ hostvars['pg01']['master_slave'] }}\" == 'master' else 'default value goes here'}}"
Make sure to use proper escaping to make the conditional statements work.
The reason this works is Since ansible internally uses python to do stuff it is a way to use ternary operator in python.
You could also generate dynamic groups based on the master/slave status:
---
- name: Play to create dynamic groups
hosts: dbservers
gather_facts: false
tasks:
- name: Create groups based on variable master_slave
group_by:
key: "database-{{ hostvars[inventory_hostname]['master_slave'] }}"
- name: Play to use the dynamic group database-master
hosts: database-master
gather_facts: false
tasks:
- name: Show hosts in group
debug:
msg: "This is {{ inventory_hostname }} from the dynamic database-master group."
The first play uses all dbservers and creates the dynamic groups based on the master_slave variable.
The dynamic groups are:
database-master containing pg01
database-slave containing pg02
The second play uses one of the dynamic created groups.
To use group_by the used variable has to exist for all hosts used.
This concept works best on automatic variable gathered by ansibles setup e.g. ansible_distribution to create dynamic groups based on the Distribution (Debian, Redhat, Ubuntu ...) or distribution versions using ansible_distribution_version.

Ansible: Convert two lists into key, value dict

I have 2 lists as set_fact and want to create a dict
I am running ansible 2.8
I have list1 as below
"inventory_devices": [
"device0",
"device1"
]
and list2 as below
"inventory_ips": [
"10.1.1.1",
"10.1.1.2"
]
I want to get an output shows like
"inventory_dict": [
"device0": "10.1.1.1",
"device1": "10.1.1.2"
]
Thanks.
You can do it entirely with jinja2 using the zip filter built into ansible.
To get a list combining the elements of other lists use zip
- name: give me list combo of two lists
debug:
msg: "{{ [1,2,3,4,5] | zip(['a','b','c','d','e','f']) | list }}"
...
Similarly to the output of the items2dict filter mentioned above, these filters can be
used to contruct a dict:
{{ dict(keys_list | zip(values_list)) }}
The zip filter sequentially combines items from pairs of lists and the dict construct creates a dictionary from a list of pairs.
inventory_dict: "{{ dict(inventory_devices | zip(inventory_ips)) }}"
here is the task to do it, populate combined var in the PB below:
---
- hosts: localhost
gather_facts: false
vars:
inventory_devices:
- device0
- device1
inventory_ips:
- 10.1.1.1
- 10.1.1.2
tasks:
- name: populate combined var
set_fact:
combined_var: "{{ combined_var|default({}) | combine({ item.0: item.1 }) }}"
loop: "{{ query('together', inventory_devices, inventory_ips) }}"
- name: print combined var
debug:
var: combined_var
result:
TASK [print combined var] **********************************************************************************************************************************************************************************************
ok: [localhost] => {
"combined_var": {
"device0": "10.1.1.1",
"device1": "10.1.1.2"
}
}
hope it helps

Filter hostvar by special propery

I have host.yml like this
---
all:
hosts:
server-a:
server_dc: "Hetzner"
ansible_host: 192.168.1.1
server-b:
server_dc: "OVH"
ansible_host: 192.168.1.2
And play book debug is:
- name: sample
debug:
var: hostvars
And all hostvars debug success.
How to get same hostvars variable but filtered. Any of that server_dc is equal OVH
I dont want to iterate for template, i just one new filtered variable that contain all other properies.
I need another variable that i debug see this output:
['server-b']
This I believe meets your requirement (removing 'no_log: true', will result in the complete dictionary being printed in your playbook output):
- set_fact:
filtered_hosts: "{{ filtered_hosts | default({}) | combine({item.key: item.value}) }}"
when: "item.value.server_dc == 'OVH'"
with_dict: "{{ hostvars }}"
no_log: true
- debug:
var: filtered_hosts

Ansible set_fact array and populate it from in loop

I want to create an array and insert value from the the array IP_TO_DNS to reversed IP address.
The idea is to restructure the IP address given in the argument to be matchable later in my code.
Code
- name: create array reversed
set_fact: reversed_ip=[]
- name: set convert ips from cli to matchable reversed ip
set_fact: reversed_ip='{{ item | regex_replace('^(?P<first_range>\d{1,3})\.(?P<second_range>\d{1,3})\.(?P<third_range>\d{1,3})\.', 'named.\\g<third_range>.\\g<second_range>.\\g<first_range>')}}'
with_items: '{{IP_TO_DNS}}'
- name: Match first block of results in path name
debug: var=item
with_items: '{{reversed_ip}}'
Output
TASK [dns : set convert ips from cli to matchable reversed ip] *****************
ok: [10.1.10.5] => (item=10.1.10.1)
ok: [10.1.10.5] => (item=10.1.10.2)
ok: [10.1.10.5] => (item=10.1.10.3)
TASK [dns : Match first block of results in path name] *************************
ok: [10.1.10.5] => (item=named.10.1.103) => {
"item": "named.10.1.103"
}
It look like my variable is not set as an array and only the first value is populate.
Any ideas ?
This is the one of the ways which I used
vars:
my_new_list: []
tasks:
- name: Get list of elements from list_vars
set_fact:
my_new_list: "{{ my_new_list + [item] }}"
with_items: "{{ list_vars }}"
You are setting the same fact three times and it gets overwritten.
You should register the output:
- name: set convert ips from cli to matchable reversed ip
set_fact: reversed_ip='{{ item | regex_replace('^(?P<first_range>\d{1,3})\.(?P<second_range>\d{1,3})\.(?P<third_range>\d{1,3})\.', 'named.\\g<third_range>.\\g<second_range>.\\g<first_range>')}}'
with_items: '{{IP_TO_DNS}}'
register: reversed_ip_results_list
- name: Match first block of results in path name
debug: var=item.ansible_facts.reversed_ip
with_items: '{{reversed_ip_results_list.results}}'
or if you want a list:
- debug: msg="{{ reversed_ip_results_list.results | map(attribute='ansible_facts.reversed_ip') | list }}"
You can assign the default of reversed_ip as a list and append the item to the list.
- name: set convert ips from cli to matchable reversed ip
set_fact: reversed_ip='{{ reversed_ip |default([]) + [item | regex_replace('^(?P<first_range>\d{1,3})\.(?P<second_range>\d{1,3})\.(?P<third_range>\d{1,3})\.', 'named.\\g<third_range>.\\g<second_range>.\\g<first_range>')] }}'
with_items: "{{ IP_TO_DNS }}"
- name: Match first block of results in path name
debug: var=item
with_items: '{{reversed_ip}}'

Resources