I want to ping several IPs with my linux server, namely:
ip 10.0.0.1 which is the host connected to the interface swp1
ip 10.0.0.2 which is the host connected to the interface swp2
with ip source 10.0.0.3 then the results are stored in a file. This command is executed through ansible with the following task:
- hosts: localhost
connection: local
gather_facts: yes
tasks:
- name: VERIFICATION // CONNECTIVITY BY PING
shell: ping {{ item }} -c 2 -s 10.0.0.3 | grep -i received | awk '{print $4}'
register: ping
loop:
- 10.0.0.1
- 10.0.0.2
- copy:
content: "{{ ping.results | map(attribute='stdout') | join('\n') }}"
dest: ping.txt
and the contents of the ping.txt as follows:
2
0
This task was executed successfully, but, how do I add the connected interface of the pinged host, so that the ping.txt become:
swp1 : 2
swp2 : 0
What script can be added to get this result?
Does this help? I needed to remove the "-s 10.0.0.3" from mine but updated and commented out the link in the playbook below. My -s is not the same I would image as it is for your OS.
- hosts: localhost
connection: local
gather_facts: yes
tasks:
- name: VERIFICATION // CONNECTIVITY BY PING
#shell: ping {{ item.ip }} -c 2 -s 10.0.0.3 | grep -i received | awk '{print "{{item.name}}:"$4}'
shell: ping {{ item.ip }} -c 2 | grep -i received | awk '{print "{{item.name}}:"$4}'
register: ping
loop:
- { ip: '10.0.0.1', name: 'swp1'}
- { ip: '10.0.0.2', name: 'swp2'}
- copy:
content: "{{ ping.results | map(attribute='stdout') | join('\n') }}"
dest: ping.txt
My ping.txt:
swp1:2
swp2:0
You can adjust the spacing within the "awk" command to get "swp1 : 2"
The interfaces of an node are accessible in the ansible_facts of the host.
Each interface has an entry with its name being a property of the ansible_facts dictionary and a list of all the existing interfaces can be accessed under ansible_facts.interfaces.
Also, the item used to create a result from a loop in a registered command can be accessed via the item property of the dictionaries under the results list.
So, you can create an intermediary fact in order to find back the name of the interface based on its IP before dumping the result to a file.
Here would be the resulting fact and copy tasks:
- set_fact:
ping_results: >-
{{
ping_results | default([]) + [
(
_interfaces_ip | zip(_interfaces_names)
| selectattr("0", "eq", item.item)
| first
).1
~ ' : '
~ item.stdout
]
}}
loop: "{{ ping.results }}"
loop_control:
label: "{{ item.item }}"
vars:
_interfaces: >-
{{
ansible_facts
| dict2items
| selectattr('key', 'in', ansible_facts.interfaces)
}}
_interfaces_names: >-
{{
_interfaces
| map(attribute="key")
}}
_interfaces_ip: >-
{{
_interfaces
| map(attribute="value.ipv4.address", default="n/a")
}}
- copy:
content: "{{ ping_results | join('\n') }}"
dest: ping.txt
Your modified playbook, should then, yield, in your case:
TASK [Gathering Facts] ***************************************************
ok: [localhost]
TASK [VERIFICATION // CONNECTIVITY BY PING] ******************************
ok: [localhost]
TASK [set_fact] **********************************************************
ok: [localhost] => (item=10.0.0.1)
ok: [localhost] => (item=10.0.0.2)
TASK [copy] **************************************************************
ok: [localhost]
And your ping.txt would contains:
swp1 : 2
swp2 : 0
Related
I'm creating vm-s with libvirt, and I would like to do a housekeeping, if I delete a host (in this example a VM) from my inventory, at the next run of the playbook, it should delete that VM's qcow2 disk from the disk pool.
I don't really get, how could I create a nested loop that iterates through the file list of that specific directory and the list of vms in my inventory, checks if the name of the vm is part of any file in the filelist, and deletes the files whose have no connection into the inventory.
Here is an example from the many things I already tried:
- name: "Housekeeping: list qcow2 disks in libvirt-pool"
find:
paths: /mnt/hdd/libvirt-pool
depth: 1
patterns:
- "*.qcow2"
register: qcow_disks
- name: debug
debug:
msg: "{{item[0]}}"
with_nested:
- "{{ qcow_disks.files | map(attribute='path') | list }}"
- "{{ groups.vm }}"
when: item[1] in item[0]
register: valid_disks
- name: debug1
debug:
msg: "invalid disks: {{ valid_disks.results | difference(all_disk) }}"
variable:
all_disk: "{{ qcow_disks.files | map(attribute='path') | list }}"
Hope you can help me out!
Thanks in advance!
I assume you have in groups.vm a list of names of VMs, without the extension .qcow2.
So the list groups.vm could looks like e.g:
['vm1', 'vm5', 'test']
The find command returns files like:
[
"/mnt/hdd/libvirt-pool/bob.qcow2",
"/mnt/hdd/libvirt-pool/daniel.qcow2",
"/mnt/hdd/libvirt-pool/test.qcow2",
"/mnt/hdd/libvirt-pool/vm1.qcow2",
"/mnt/hdd/libvirt-pool/vm5.qcow2"
]
With the following command you can reduce this list to the name without extension, then you can easily compare the lists.
{{ qcow_disks.files | map(attribute='path') | map('basename') | map('splitext') | map('first') }}
basename returns the filename, without preceding path
splitext splits the filename into a list: [name, extension]
first takes the first element from the list, i.e. the name
More on basename and splitext in the Ansible docs.
{{ found_disks | reject('in', current_vms) }}
With the reject filter you can then discard the current elements, so that you contain a list with all old VMs.
The following tasks:
- name: "Housekeeping: list qcow2 disks in libvirt-pool"
find:
paths: /mnt/hdd/libvirt-pool
depth: 1
patterns:
- "*.qcow2"
register: qcow_disks
- debug:
msg: "{{ qcow_disks.files | map(attribute='path') }}"
- debug:
msg: "{{ old_disks }}"
vars:
current_vms: ['vm1', 'vm5', 'test']
found_disks: "{{ qcow_disks.files | map(attribute='path') | map('basename') | map('splitext') | map('first') }}"
old_disks: "{{ found_disks | reject('in', current_vms) }}"
Note: current_vms corresponds to the list you have via groups.vm.
return this result:
TASK [Housekeeping: list qcow2 disks in libvirt-pool] ************************
ok: [localhost]
TASK [debug] *****************************************************************
ok: [localhost] => {
"msg": [
"/mnt/hdd/libvirt-pool/bob.qcow2",
"/mnt/hdd/libvirt-pool/daniel.qcow2",
"/mnt/hdd/libvirt-pool/test.qcow2",
"/mnt/hdd/libvirt-pool/vm1.qcow2",
"/mnt/hdd/libvirt-pool/vm5.qcow2"
]
}
TASK [debug] *****************************************************************
ok: [localhost] => {
"msg": [
"bob",
"daniel"
]
}
I hope this helps you.
Imagine this dict on 4 different hosts.
# on host 1
my_dict:
ip: 10.0.0.111
roles:
- name: something
observer: false
# on host 2
my_dict:
ip: 10.0.0.112
roles:
- name: something
observer: false
# on host 3
my_dict:
ip: 10.0.0.113
roles:
- name: something
observer: true
# on host 4
my_dict:
ip: 10.0.0.114
roles:
- name: whatever
When Ansible runs for all 4 hosts I want it to build a variable for each host having the roles name 'something'. The desired output is:
10.0.0.111 10.0.0.112 10.0.0.113:observer
There are 2 requirements:
when my_dict.roles.name == 'something' it must add the ip to the var
but when my_dict.roles.observer , it must add the ip + ':observer'
I eventually want to use the var in a Jinja template, so to me, the var can be either set via an Ansible task or as a jinja template.
This doesn't work:
- name: set fact for ip
debug:
msg: >-
{{ ansible_play_hosts |
map('extract', hostvars, ['my_dict', 'ip'] ) |
join(' ') }}
when: ???
You could create two lists:
one with what should be postfixed to the IPs with the condition based on the observer property
the other one with the IPs
And then zip them back together.
Given:
- debug:
msg: >-
{{
_ips | zip(_is_observer) | map('join') | join(' ')
}}
vars:
_hosts: >-
{{
hostvars
| dict2items
| selectattr('key', 'in', ansible_play_hosts)
| selectattr('value.my_dict.roles.0.name', '==', 'something')
}}
_is_observer: >-
{{
_hosts
| map(attribute='value.my_dict.roles.0.observer')
| map('replace', false, '')
| map('replace', true, ':observer')
}}
_ips: >-
{{
_hosts
| map(attribute='value.my_dict.ip')
}}
This yields:
TASK [debug] *************************************************************
ok: [localhost] =>
msg: 10.0.0.111 10.0.0.112 10.0.0.113:observer
I have to check if a list of mount points are available on the system.
So, I defined a variable with the list of mount points then extracted the available mount points from Ansible facts.
---
- hosts: all
vars:
required_mounts:
- /prom/data
- /prom/logs
tasks:
- name: debug mountpoint
set_fact:
mount_points: "{{ ansible_mounts|json_query('[].mount') }}"
- name: check fs
fail:
msg: 'mount point not found'
when: required_mounts not in mount_points
I am stuck here, I don't know how to compare the variable required_mounts with existing mount points.
If any item in required_mounts is not in the existing mount points the task should fail.
The task check fs always fail, even if the mount points are present.
Do I have to loop one by one? And compare item by item? If so, how can I achieve this?
You can use the set theory for this, since what you are looking for is simply the difference between the required_mounts and the ansible_mounts.
Also, there is no need for a JMESPath query here, this simple requirement can be achieved with a simple map.
So, this can be achieved with the task alone:
- fail:
msg: "Missing mounts: `{{ missing_mounts | join(', ') }}`"
when: missing_mounts | length > 0
vars:
missing_mounts: >-
{{
required_mounts
| difference(
ansible_mounts | map(attribute='mount')
)
}}
Given the playbook:
- hosts: localhost
gather_facts: yes
vars:
required_mounts:
- /etc/hostname
- /etc/hosts
- /tmp/not_an_actual_mount
- /tmp/not_a_mount_either
tasks:
- fail:
msg: "Missing mounts: `{{ missing_mounts | join(', ') }}`"
when: missing_mounts | length > 0
vars:
missing_mounts: >-
{{
required_mounts
| difference(
ansible_mounts | map(attribute='mount')
)
}}
This yields:
TASK [Gathering Facts] *******************************************************
ok: [localhost]
TASK [fail] ******************************************************************
fatal: [localhost]: FAILED! => changed=false
msg: 'Missing mounts: `/tmp/not_an_actual_mount, /tmp/not_a_mount_either`'
How to get the sum of two hosts with Jinja2 filtering ansible
host1 and host 2
---
- name: Count Check
hosts: MYGROUP
gather_facts: true
user: sv_admin
tasks:
- name: count check
shell: cat /etc/hosts | wc -l
register: command_result
- debug:
var: command_result.stdout
- set_fact:
total_result: "{{ command_result.stdout | map('int') | sum(start=0) }}"
- debug:
msg: "Total count: {{ total_result }}"
Playbook Output
TASK [debug] *****************************************************************
ok: [Host-01] => {
"msg": "Total count: 134"
}
ok: [Host-02] => {
"msg": "Total count: 133"
}
Use extract and sum. For example, the playbook below
shell> cat playbook.yml
- hosts: test_01:test_03
gather_facts: false
tasks:
- shell: cat /etc/hosts | wc -l
register: command_result
- debug:
var: command_result.stdout
- set_fact:
total_result: "{{ ansible_play_hosts_all|
map('extract', hostvars, ['command_result', 'stdout'])|
map('int')|
sum }}"
run_once: true
- debug:
var: total_result
gives (abridged)
shell> ansible-playbook playbook.yml
PLAY [test_01:test_03] ****
TASK [shell] ****
changed: [test_01]
changed: [test_03]
TASK [debug] ****
ok: [test_01] => {
"command_result.stdout": " 62"
}
ok: [test_03] => {
"command_result.stdout": " 31"
}
TASK [set_fact] ****
ok: [test_01]
TASK [debug] ****
ok: [test_03] => {
"total_result": "93"
}
ok: [test_01] => {
"total_result": "93"
}
See serial
See the difference between ansible_play_hosts and ansible_play_hosts_all
You can use custom stats to do that: https://docs.ansible.com/ansible/latest/modules/set_stats_module.html
So for your case it would look like
---
- name: Count Check
hosts: MYGROUP
gather_facts: true
user: sv_admin
tasks:
- name: count check
shell: cat /etc/hosts | wc -l
register: command_result
- debug:
var: command_result.stdout
- set_fact:
host_result: "{{ command_result.stdout }}"
- debug:
msg: "Count for this host: {{ host_result }}"
- set_stats:
data: "{{ { 'total_count': host_result | int } }}"
Then if you run it with ANSIBLE_SHOW_CUSTOM_STATS=yes it will show you the result at the end:
$ ANSIBLE_SHOW_CUSTOM_STATS=yes ansible-playbook -i inventory pb.yml
... (usual output)
CUSTOM STATS: *************************************************************
RUN: { "total_count": 267}
The set_stats task adds results together from all the hosts by default, which is what you are looking for. You need to make sure the values are integers though, because if they are strings it will just concatenate them and you will end up with something like RUN: { "total_count": "134133"}. That's why I have put the data: bit the way I have - if you try to create the dictionary in regular yaml, like
data:
total_count: "{{ host_result | int }}"
you will see that the value is still a string (due to the way yaml/jinja works) and it won't work properly.
Trying to add \ before . on a list of IPs in Ansible.
Example:
"msg": "192.168.5.0"
Expected output:
"msg": "192\.168\.5\.0"
I tried this with no luck.
---
- hosts: localhost
vars:
ip: "{{ ansible_default_ipv4.network }}"
tasks:
- debug: msg="{{ ip | regex_replace('\.', '\\.')}}"
Output:
ok: [localhost] => {
"msg": "192\\.168\\.5\\.0"
}
---
- hosts: localhost
vars:
ip: "{{ ansible_default_ipv4.network }}"
tasks:
- debug: msg="{{ ip | regex_replace('.', '\.')}}"
Output:
ok: [localhost] => {
"msg": "192\\.168\\.5\\.0"
}
The function regex_replace works as you expect. The double backslashes you see are the evaluation of the string by debug. Display, for example, the length of the string
- set_fact:
ip2: "{{ ip | regex_replace(myregex, myreplace) }}"
vars:
myregex: '\.'
myreplace: '\.'
- debug:
msg: "length of {{ ip2 }} is {{ ip2|length }}"
give
"msg": "length of 192\\.168\\.5\\.0 is 14"
It's also possible to write the string to a file. For example the template
shell> cat test.txt.j2
{{ ip2 }}
and the task
- template:
src: test.txt.j2
dest: test.txt
give
shell> cat test.txt
192\.168\.5\.0