Ansible get first element from list - ansible

Suppose I have the following vars_file:
mappings:
- primary: 1.1.1.1
secondary: 2.2.2.2
- primary: 12.12.12.12
secondary: 11.11.11.11
and hosts file
1.1.1.1
12.12.12.12
5.5.5.5
and the following playbook task
- name: Extract secondary from list
debug:
msg: "{{ (mappings | selectattr('primary', 'search', inventory_hostname) | list | first | default({'secondary':None})).secondary }}"
The current task works and will give empty string when no match are found, but I would like to know if there is a better way/cleaner way of doing it without passing a dictionary to the default constructor.

An option would be to use json_query
- debug:
msg: "{{ mappings | json_query(\"[?primary=='\" + inventory_hostname + \"'].secondary\") }}"
, but selectattr works too
- debug:
msg: "{{ mappings | selectattr('primary', 'equalto', inventory_hostname) | map(attribute='secondary') | list }}"

Related

Ansible | delete files from a directory if the filename doesn't contains any of the strings from a list

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.

Ansible: merge dictionaries appending values

How can I get a dictionary with values from input separated with a comma? There can be a different number and order of input parameters. What I've tried just gives the error below
- set_fact:
input:
- port: 1234
protocol: TCP
messages: 888-999
file: s3://somepath/file.xsl
- protocol: TLS
port: 5678
path: s3://somepath/mycertificate.crt
messages: 345, 467, 888
file: s3://somepath/file2.xsl
- set_fact:
final_dict:
finalFile: item | map(attribute='file')| join(',')
finalFilter: item | map(attribute='messages')| join(',')
finalPath: item | map(attribute='path')| join(',')
finalProtocol: item | map(attribute='protocol')| join(',')
finalPort: item | map(attribute='port')| join(',')
loop: "{{ input }}"
The error message is as follows:
>"msg": "The task includes an option with an undefined variable. The error was: 'str object' has no attribute 'file'
You do have three issues here:
if you intend to use map, then you need to do it on a list, so, you should have expressions like
var: input | map(attribute='file')
And not act on the item of a loop.
you are missing the {{ ... }} expression delimiters in your final_dict, e.g.:
finalFile: "{{ input | map(attribute='file') | join(',') }}"
and not
finalFile: input | map(attribute='file') | join(',')
Because you do have some undefined keys in your list of dictionaries input, you want to use the default value of map:
finalPath: "{{ input | map(attribute='path', default='') | join(',') }}"
(optionally) if you are going to use input only in that context, make it a variable block. You can define it at the level of the task, play, inventory, ...
Given these three remarks, those two tasks:
- set_fact:
final_dict:
finalFile: "{{ input | map(attribute='file') | join(',') }}"
finalFilter: "{{ input | map(attribute='messages') | join(',') }}"
finalPath: "{{ input | map(attribute='path', default='') | join(',') }}"
finalProtocol: "{{ input | map(attribute='protocol') | join(',') }}"
finalPort: "{{ input | map(attribute='port') | join(',') }}"
vars:
input:
- port: 1234
protocol: TCP
messages: 888-999
file: s3://somepath/file.xsl
- protocol: TLS
port: 5678
path: s3://somepath/mycertificate.crt
messages: 345, 467, 888
file: s3://somepath/file2.xsl
- debug:
var: final_dict
Would yield:
ok: [localhost] =>
final_dict:
finalFile: s3://somepath/file.xsl,s3://somepath/file2.xsl
finalFilter: 888-999,345, 467, 888
finalPath: ',s3://somepath/mycertificate.crt'
finalPort: 1234,5678
finalProtocol: TCP,TLS

Regex match for key in jinja2 selectattr()

Given the data:
"serverName", [
serverData: [
{
"internal_ip": "10.1.1.100",
"external_ip": "172.16.1.10",
"name": "dns-1"
},
],
]
This extracts the name value dns-1 when the internal_ip matches the equalto. So far, so good.
- debug:
msg: "{{ mydict | selectattr('internal_ip', 'equalto', '10.1.1.100') |
map(attribute='name') | list }}"
In the real problem, I do not know which type of *_ip will the ip address I'm searching for will reside. It could be under internal_ip, it could be under external_ip and for all I know, there could be even more options, the only thing that will always be there - is the actual IP address I'm searching for: '10.1.1.100`.
So I need to regex match like so:
- debug:
msg: "{{ mydict | selectattr('^.*$', 'equalto', '10.1.1.100') |
map(attribute='name') | list }}"
I'm not sure if this is possible, but it seems to be one of the ways out of this jam.
For example, the playbook
shell> cat playbook.yml
- hosts: localhost
vars:
mylist:
- {internal_ip: 10.1.1.101, external_ip: 172.16.1.10, name: dns-1}
- {internal_ip: 10.1.1.102, external_ip: 172.16.1.10, name: dns-2}
- {internal_ip: 10.1.1.103, external_ip: 172.16.1.10, name: dns-3}
tasks:
- set_fact:
sel: "{{ sel|default([]) + [item.name] }}"
loop: "{{ mylist }}"
when: sel_ip|default('') in item.values()|list
- debug:
var: sel
gives
shell> ansible-playbook playbook.yml -e sel_ip=172.16.1.10
...
sel:
- dns-1
- dns-2
- dns-3
shell> ansible-playbook playbook.yml -e sel_ip=10.1.1.103
...
sel:
- dns-3

Ansible: how to filter using varible

i have ansible-playbook that gives list of lines in debug output.
I am able to filter debug OUTPUT using a string (exp: CUST) but I am struggling to filter the list using a variable.
- debug:
msg: "{{ List.msg | select('match', '^(CUST)[0-9]+') | list }}"
List msg output:
CUST1
CUST2
NEW1
NEW2
from the above debug command, i get CUST1, CUST2 in filtered output.
- set_fact:
filter: "{{ fileout.results[0].content }}"
above filter generates "CUST" and i want to use this filter variable in above debug command.
using below syntax i get nothing, may be ansible is NOT taking it as appropriate variable.
- debug:
msg: "{{ List.msg | select('match', '^("{{ filter }}")[0-9]+') | list }}"
Please help.
thanks in advance.
It's possible to isolate the declaration of the regex filter and simplify the quotation. For example
vars:
List:
msg: ['CUST1','CUST2','NEW1','NEW2']
Patterns: ['CUST','NEW']
tasks:
- debug:
msg: "{{ List.msg | select('match', filter) | list }}"
vars:
filter: '^{{ item }}[0-9]+'
loop: "{{ Patterns }}"
gives
"msg": [
"CUST1",
"CUST2"
]
"msg": [
"NEW1",
"NEW2"
]

Ansible returns only one item from a dictionary

I'm trying to get a list of IPs from an specific Service, and ansible returns only one item from the loop.
I have tried many things and is always the same result.
Need help.
- name: "Amazon IPs"
include_vars:
file: /home/user1/ansible/AWS/ip-ranges.json
name: amazon
# - set_fact:
# # # test: "{{ (variable.stdout | from_json).prefixes | map(attribute='ip_prefix') | list }}"
# amazonipv4: "{{ item }}"
# # amazonipv6: "{{ amazon.ipv6_prefixes | map(attribute='ipv6_prefix') | list }}"
# loop: "{{amazon.prefixes | map(attribute='ip_prefix') | list }}"
# # when: '"AMAZON" in item.service'
- set_fact:
test3: "{{item.ip_prefix}}"
loop: "{{amazon.prefixes | list }}"
when: '"AMAZON" in item.service'
- debug:
var: test3
I expect to get a list based on the service, but I only get one item.
example:
TASK [debug] ***********************************************
ok: [localhost] => {
"test3": "54.190.198.32/28"
in each iteration in the set_fact loop, you are setting the value, not pushing to a list. you need to change your syntax to:
- set_fact:
test3: "{{ test3 | default([]) + [item.ip_prefix] }}"
hope it helps.

Resources