Get facts when var defined in hostvars - ansible

ansible 2.9.1
I have inventory:
[group1]
server1 master=yes
server2 master=no
server3 master=no
How get ansible_fqdn with master=yes from server2, server3?
Example:
server2 or server3 facts:
...
master_server: server1
...
I think so, but it did not work:
- name: set fact
set_fact:
master_server: {{ ansible_fqdn }}
when: master == 'yes'
delegate_to: "{{ item }}"
loop: "{{ ansible_play_hosts }}"
UPDATE. RESOLVE
add:
run_once: True

Q: "How to get ansible_fqdn with master=yes from server2, server3?"
A: There are more options. For example, either use selectattr
- hosts: group1
tasks:
- set_fact:
master_server: "{{ (groups.group1|
map('extract', hostvars)|
selectattr('master', 'eq', True)|
list|
first).ansible_fqdn }}"
run_once: true
- debug:
var: master_server
, or use json_query
- hosts: group1
tasks:
- set_fact:
master_server: "{{ groups.group1|
map('extract', hostvars)|
list|
json_query('[?master].ansible_fqdn')|
first }}"
run_once: true
- debug:
var: master_server
Both options give
ok: [test_02] => {
"master_server": "test_01.example.org"
}
ok: [test_01] => {
"master_server": "test_01.example.org"
}
ok: [test_03] => {
"master_server": "test_01.example.org"
}
Inventory used in the examples
$ cat hosts
group1:
hosts:
test_01:
master: yes
ansible_fqdn: test_01.example.org
test_02:
master: no
ansible_fqdn: test_02.example.org
test_03:
master: no
ansible_fqdn: test_03.example.org

Related

Looping over ansible_hosts from inventory

I have a bunch of root servers with different IP addresses. I'm trying to configure the ufw firewall on the server with MySQL server to only allow access from my servers (might change to webservers later) with Ansible. Initially, I only had the FQDNs in the inventory but added the ansible_host IPs because the firewall is not going to resolve the host names (makes sense).
Unfortunately I do not know how to access the ansible_host in the loop query("inventory_hostnames", "all")
My inventory:
all:
vars:
ansible_ssh_user: me
children:
sqlserver:
hosts:
sqlserver.my-domain.de:
ansible_ssh_user: mysql_me_user
ansible_host: 1.1.1.1
webserver:
hosts:
webserver1.my-domain.de:
ansible_host: 2.2.2.2
webserver2.my-domain.de:
ansible_host: 3.3.3.3
now I am trying to loop in the playbook:
- hosts: '{{target|default("sqlserver")}}'
roles:
- { name: oefenweb.ufw, become: yes } # needs root but does not define become by itself...
vars:
ufw_logging: true
ufw_rules:
- rule: allow
to_port: 22
protocol: tcp
tasks:
- name: open MySQL for servers in my
ufw:
rule: allow
to_port: 3306
protocol: tcp
from_ip: '{{ item }}'
loop: '{{ hosts|default(query("inventory_hostnames", "all")) }}'
tags: test
become: true
There are many options. Make your choice depending on the use case.
For example, the play below
shell> cat pb.yml
- hosts: '{{ target|default("sqlserver") }}'
tasks:
- debug:
msg: "{{ item }}: {{ ansible_host }}"
loop: "{{ clients|default(groups.webserver) }}"
vars:
ansible_host: "{{ hostvars[item].ansible_host }}"
gives
shell> ansible-playbook pb.yml
PLAY [sqlserver] *****************************************************************************
TASK [debug] *********************************************************************************
ok: [sqlserver.my-domain.de] => (item=webserver1.my-domain.de) =>
msg: 'webserver1.my-domain.de: 2.2.2.2'
ok: [sqlserver.my-domain.de] => (item=webserver2.my-domain.de) =>
msg: 'webserver2.my-domain.de: 3.3.3.3'
PLAY RECAP ***********************************************************************************
sqlserver.my-domain.de: ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Example of the project for testing
shell> tree .
.
├── ansible.cfg
├── hosts
└── pb.yml
0 directories, 3 files
shell> cat ansible.cfg
[defaults]
gathering = explicit
inventory = $PWD/hosts
stdout_callback = yaml
shell> cat hosts
all:
vars:
ansible_ssh_user: me
children:
sqlserver:
hosts:
sqlserver.my-domain.de:
ansible_ssh_user: mysql_me_user
ansible_host: 1.1.1.1
webserver:
hosts:
webserver1.my-domain.de:
ansible_host: 2.2.2.2
webserver2.my-domain.de:
ansible_host: 3.3.3.3
shell> cat pb.yml
- hosts: '{{ target|default("sqlserver") }}'
tasks:
- debug:
msg: "{{ item }}: {{ ansible_host }}"
loop: "{{ clients|default(groups.webserver) }}"
vars:
ansible_host: "{{ hostvars[item].ansible_host }}"
Create the list and the dictionary below
clients: "{{ groups.webserver }}"
ah_list: "{{ clients|map('extract', hostvars, 'ansible_host')|list }}"
ah_dict: "{{ dict(clients|zip(ah_list)) }}"
give
ah_list:
- 2.2.2.2
- 3.3.3.3
ah_dict:
webserver1.my-domain.de: 2.2.2.2
webserver2.my-domain.de: 3.3.3.3
Then, all tasks below give the same results
2a)
- debug:
msg: "{{ item.key }}: {{ item.value }}"
with_dict: "{{ ah_dict }}"
2b)
- debug:
msg: "{{ item.0 }}: {{ item.1 }}"
with_together:
- "{{ clients }}"
- "{{ ah_list }}"
2c)
- debug:
msg: "{{ item }}: {{ ah_dict[item] }}"
loop: "{{ clients }}"
Example of a complete playbook for testing
- hosts: '{{ target|default("sqlserver") }}'
vars:
clients: "{{ groups.webserver }}"
ah_list: "{{ clients|map('extract', hostvars, 'ansible_host')|list }}"
ah_dict: "{{ dict(clients|zip(ah_list)) }}"
tasks:
- debug:
var: ah_list
- debug:
var: ah_dict
- debug:
msg: "{{ item.key }}: {{ item.value }}"
with_dict: "{{ ah_dict }}"
- debug:
msg: "{{ item.0 }}: {{ item.1 }}"
with_together:
- "{{ clients }}"
- "{{ ah_list }}"
- debug:
msg: "{{ item }}: {{ ah_dict[item] }}"
loop: "{{ clients }}"
As I was writting the question I understood that should google for accessing inventory property and found a solution in Accessing inventory host variable in Ansible playbook - use
from_ip: '{{ hostvars[item].ansible_host }}'
for my task.
I hope that this the way to go.

how to make a list from ansible_facts with multiple hosts

I'm trying to make a list with IP addresses of various hosts and then use this list in another task. My question is, how can I pick an IP (I need the public IP) from the output of each host and add it to a list? I need the IPs that do not start with 10..
Later, I need to use this list in another task.
I extract this information by running this playbook:
- hosts: facts
become: true
gather_facts: True
tasks:
- debug:
msg: "The ip: {{ item }}"
with_items: "{{ ansible_all_ipv4_addresses }}"
Later, I need to use this list in another task:
- wait_for:
host: "{{ item[0] }}"
port: "{{ item[1] }}"
state: started
delay: 0
timeout: 2
delegate_to: localhost
become: false
ignore_errors: no
ignore_unreachable: yes
register: result
failed_when: not result.failed
with_nested:
- [ IP LIST HERE]
- [443,80,9200,9300,22,5432,6432]
You can access those values from the hostvars right away, then use a reject filter with a match test in order to reject what you don't want to test for.
Which, in a debug task would gives:
# note: ports list reduced for brevity
- debug:
msg: "I should wait for interface {{ item.0 }}:{{ item.1 }}"
loop: >-
{{
hostvars
| dict2items
| selectattr('key', 'in', ansible_play_hosts)
| map(attribute='value.ansible_all_ipv4_addresses', default=[])
| flatten
| reject('match', '10\..*')
| product(_ports)
}}
loop_control:
label: "{{ item.0 }}"
run_once: true
delegate_to: localhost
vars:
_ports:
- 22
- 80
In my lab, this give:
ok: [ansible-node-1 -> localhost] => (item=172.18.0.3) =>
msg: I should wait for interface 172.18.0.3:22
ok: [ansible-node-1 -> localhost] => (item=172.18.0.3) =>
msg: I should wait for interface 172.18.0.3:80
ok: [ansible-node-1 -> localhost] => (item=172.18.0.4) =>
msg: I should wait for interface 172.18.0.4:22
ok: [ansible-node-1 -> localhost] => (item=172.18.0.4) =>
msg: I should wait for interface 172.18.0.4:80
Try the example below
shell> cat pb.yml
- hosts: all
vars:
ip_list: "{{ ansible_play_hosts|
map('extract', hostvars, 'ansible_all_ipv4_addresses')|
map('first')|list }}"
ip_list_reject: "{{ ip_list|reject('match', '10\\.')|list }}"
tasks:
- setup:
gather_subset: network
- block:
- debug:
var: ip_list
- debug:
var: ip_list_reject
- wait_for:
host: "{{ item.0 }}"
port: "{{ item.1 }}"
state: started
delay: 0
timeout: 2
delegate_to: localhost
register: result
with_nested:
- "{{ ip_list_reject }}"
- [443, 80, 9200, 9300, 22, 5432, 6432]
run_once: true

search a host in ansible inventory

I have below ansible inventory structure:
[app1]
labhost1 ansible_host=1.1.1.1 tag=master
labhost2 ansible_host=1.1.1.2 tag=slave
labhost3 ansible_host=1.1.1.3 tag=slave
labhost4 ansible_host=1.1.1.4 tag=master
labhost5 ansible_host=1.1.1.5 tag=slave
labhost6 ansible_host=1.1.1.6 tag=slave
[DC1]
dc1_app1
[DC2]
dc2_app1
[dc1_app1]
labhost1 ansible_host=1.1.1.1 tag=master
labhost2 ansible_host=1.1.1.2 tag=slave
labhost3 ansible_host=1.1.1.3 tag=slave
[dc2_app1]
labhost4 ansible_host=1.1.1.4 tag=master
labhost5 ansible_host=1.1.1.5 tag=slave
labhost6 ansible_host=1.1.1.6 tag=slave
and group_vars file below:
DC1.yml
---
location: country1
DC2.yml
---
location: country2
When running a playbook on labhost2, I like to extract the IP address of the master device in the same datacenter in which the host labhost2 is located.
I tried below expression
- set_fact:
masterIP: "{{ groups['app1'] | map('extract', hostvars) | selectattr('location', 'eq', location) | selectattr('tag', 'eq', 'master') | map(attribute='ansible_host') }}"
It should return 1.1.1.1 as value of the variable masterIP but it shows:
VARIABLE IS UNDEFINED
From the logic of the question, I can only assume that you run the playbook for the group app1
- hosts: app1
Iterate the 'dc*' groups that belong to the group of applications and create the dictionary of masters, e.g.
- set_fact:
dc_masters: "{{ dc_masters|d({})|combine({item: _dict.master}) }}"
loop: "{{ groups|select('match', 'dc[\\d+]_app1') }}"
vars:
_hosts: "{{ groups[item] }}"
_tags: "{{ _hosts|map('extract', hostvars, 'tag') }}"
_dict: "{{ dict(_tags|zip(_hosts)) }}"
run_once: true
gives
dc_masters:
dc1_app1: labhost1
dc2_app1: labhost4
Then you can use this dictionary and find the masters for the slaves, e.g.
- debug:
msg: "slave: {{ inventory_hostname }} master: {{ dc_masters[dc] }}"
vars:
dc: "{{ group_names|difference(['app1'])|first }}"
when: tag == 'slave'
gives
TASK [debug] **************************************************************
skipping: [labhost1]
skipping: [labhost4]
ok: [labhost2] =>
msg: 'slave: labhost2 master: labhost1'
ok: [labhost3] =>
msg: 'slave: labhost3 master: labhost1'
ok: [labhost6] =>
msg: 'slave: labhost6 master: labhost4'
ok: [labhost5] =>
msg: 'slave: labhost5 master: labhost4'
Fit the expression dc: ... to your needs if there are more groups a slave belongs to.

Invert a dictionary with list values in ansible

How do I convert a dictionary like below
{
"host1": ["tag1", "tag2"],
"host2": ["tag1"]
}
to below in ansible:
{
"tag1": ["host1","host2"],
"tag2": ["host1"]
}
I have been trying to do this but got stuck due to the value being a list.
The playbook
- hosts: localhost
vars:
dict1:
host1: [tag1, tag2]
host2: [tag1]
tasks:
- set_fact:
dict2: "{{ dict2|default({})|combine({item: list_of_hosts}) }}"
loop: "{{ dict1.values()|flatten|unique }}"
vars:
list_of_hosts: "{{ dict1|
dict2items|
selectattr('value', 'contains', item)|
map(attribute='key')|
list }}"
- debug:
var: dict2
gives
dict2:
tag1: [host1, host2]
tag2: [host1]
Optionally, use json_query
vars:
list_of_hosts: "{{ dict1|dict2items|json_query(_query) }}"
_query: '[?value.contains(#, `{{ item }}`)].key'

How do i count task success/failure in ansible?

I am using ansible to set up a distributed application. i'm installing nodes, and then creating virtual interfaces, and cannot have more virtual interfaces than nodes. therefore, if i install on X nodes, and Y nodes fail, I need to check there are no more that (X-Y) virtual interfaces.
Is there a way to get, for a specific task, a numerical value of how many nodes succeeded/failed, so i can later use it to check the number of virtual interfaces?
Use ansible-runner. See Runner Artifact Job Events and "stats" in particular. For example ansible-runner and the playbook
shell> cat private3/project/test.yml
- hosts: test_01:test_02
gather_facts: false
tasks:
- debug:
var: inventory_hostname
- fail:
msg: Fail test_02
when: inventory_hostname == 'test_02'
shell> ansible-runner -p test.yml -i ID01 run private3
...
ASK [fail] ********************************************************************
skipping: [test_01]
fatal: [test_02]: FAILED! => {"changed": false, "msg": "Fail test_02"}
...
created records in the directory private3/artifacts/ID01/job_events/. I'm not aware of any publicly available tool to analyze the events. I've created a playbook that displays failed tasks
shell> cat pb.yml
- hosts: localhost
gather_facts: false
vars:
events_dir: private3/artifacts/ID01/job_events
tasks:
- find:
paths: "{{ events_dir }}"
register: result
- include_vars:
file: "{{ item }}"
name: "{{ 'my_var_' ~ my_idx }}"
loop: "{{ result.files|json_query('[].path') }}"
loop_control:
index_var: my_idx
label: "{{ my_idx }}"
- set_fact:
my_events: "{{ my_events|default({})|
combine({my_key: lookup('vars', my_key)}) }}"
loop: "{{ range(0, result.matched)|list }}"
loop_control:
index_var: my_idx
vars:
my_key: "{{ 'my_var_' ~ my_idx }}"
- set_fact:
my_list: "{{ my_events|json_query('*.{counter: counter,
event: event,
task: event_data.task_action,
host: event_data.host}') }}"
- debug:
var: item
loop: "{{ my_list|sort(attribute='counter') }}"
loop_control:
label: "{{ item.counter }}"
when: item.event == 'runner_on_failed'
gives
shell> ansible-playbook pb.yml
...
skipping: [localhost] => (item=11)
ok: [localhost] => (item=12) => {
"ansible_loop_var": "item",
"item": {
"counter": 12,
"event": "runner_on_failed",
"host": "test_02",
"task": "fail"
}
}
skipping: [localhost] => (item=13)
...
Feel free to fit the playbook to your needs.

Resources