ansible-playbook with_flattened migration to loop - ansible

I am trying to migrate my old playbooks that uses with_flattened to loop.
I tried to follow ansible user guide but failed to do so.
This is my host_var:
- hosts: example.com
vars:
- configureddisks:
- xvdb
- xvdc
- btrfsdisks:
- xvdf
- xvdg
My original task and its output is as follow:
# Task
- debug:
msg: "{{ item }}"
with_flattened:
- "{{ configureddisks | select('defined') | list }}"
- "{{ btrfsdisks | select('defined') | list }}"
# ansible-playbook output
TASK [devices : debug] **********************************************************************************************************************************************************************************************************************
ok: [example.com] => (item=xvdb) => {
"msg": "xvdb"
}
ok: [example.com] => (item=xvdc) => {
"msg": "xvdc"
}
ok: [example.com] => (item=xvdf) => {
"msg": "xvdf"
}
ok: [example.com] => (item=xvdg) => {
"msg": "xvdg"
}
My new task and its output is as follow:
# Task
- debug:
msg: "{{ item }}"
loop:
- "{{ configureddisks | select('defined') | list | flatten }}"
- "{{ btrfsdisks | select('defined') | list | flatten }}"
# ansible-playbook output
ok: [example.com] => (item=[u'xvdb', u'xvdc']) => {
"msg": [
"xvdb",
"xvdc"
]
}
ok: [example.com] => (item=[u'xvdf', u'xvdg']) => {
"msg": [
"xvdf",
"xvdg"
]
}
How should I write the new task using loop so that it has the same outputs as the old task?

You are misuing the flatten filter. When you write this:
loop:
- "{{ configureddisks | select('defined') | list | flatten }}"
- "{{ btrfsdisks | select('defined') | list | flatten }}"
The flatten filter has no effect: you are providing as input (twice) a list that is already flattened. You would need to apply the filter to the generated list, but rather than doing that, you can rewrite your expression so that no flattening is necessary:
- debug:
msg: "{{ item }}"
loop: "{{ (configureddisks + btrfsdisks) | select('defined') | list }}"
If you really wanted to go the "build a list of lists and flatten it" route, that might look something like:
- debug:
msg: "{{ item }}"
loop: >-
{{
(
(configureddisks | select('defined') | list) +
(btrfsdisks | select('defined') | list)
)|flatten
}}
Note that I've spread that across multiple lines for legibility, but you could just as easily write it all on one line:
loop: "{{ ((configureddisks | select('defined') | list) + (btrfsdisks | select('defined') | list))|flatten }}"

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 compare DNS list from F5

I would like to create a playbook which logs into a big-ip device, downloads a list of certificates and compares it with a list of existing certificates to find matches.
My current playbook is:
---
- name: App Collection f5_modules
hosts: localhost
connection: local
gather_facts: false
vars:
from_ex_var: ["test1.com","test.com","httpsN-dep.san-dc.yu.com"]
from_F5: [
{
"createDateTime": "2022-11-27T09:23:39.000Z",
"expirationDate": 1681379023,
"expirationDateTime": "2023-04-13T09:43:40.000Z",
"subject": "CN=test.com,O=chicago,L=los,ST=Nord,C=PE",
"subjectAlternativeName": "DNS:httpsN-dep.san-dc.yu.com, DNS:test1.com, DNS:test.com",
},
{
"createDateTime": "2023-11-27T09:23:39.000Z",
"expirationDate": 1781379023,
"expirationDateTime": "2023-04-13T09:43:40.000Z",
"subject": "CN=test.com,O=chicago,L=los,ST=Nord,C=PE",
"subjectAlternativeName": "DNS:test.com, DNS:test1.com, DNS:httpsN-dep.san-dc.yu.com",
}
]
tasks:
- name: debug 0
set_fact:
alt_name: "{{ from_ex_var | map('regex_replace', '^(.*)$', 'DNS:\\1') | list | sort }}"
- name: works
debug:
msg: "{{ item.subjectAlternativeName.split(',') | map('regex_replace', '.*DNS:(.*)', 'DNS:\\1') | list | sort }}"
loop: "{{ from_F5 }}"
- name: does not work
debug:
var: item.subjectAlternativeName.split(',') | map('regex_replace', '.*DNS:(.*)', 'DNS:\\1') | list | sort
loop: "{{ from_F5 }}"
- name: checking compare two list
debug:
msg: "works"
loop: "{{ from_F5 }}"
when: item.subjectAlternativeName.split(',') | map('regex_replace', '.*DNS:(.*)', 'DNS:\\1') | list | sort == alt_name
I think the problem is that DNS:\\1 gives me a wrong value in my regex_replace expression in the last when: line as I tried to debug in the following task which gives:
TASK [does not work] *****************************************************************************************************************************************************************************************
ok: [localhost] => (item={'createDateTime': '2022-11-27T09:23:39.000Z', 'expirationDate': 1681379023, 'expirationDateTime': '2023-04-13T09:43:40.000Z', 'subject': 'CN=test.com,O=chicago,L=los,ST=Nord,C=PE', 'subjectAlternativeName': 'DNS:httpsN-dep.san-dc.yu.com, DNS:test1.com, DNS:test.com'}) => {
"ansible_loop_var": "item",
"item": {
"createDateTime": "2022-11-27T09:23:39.000Z",
"expirationDate": 1681379023,
"expirationDateTime": "2023-04-13T09:43:40.000Z",
"subject": "CN=test.com,O=chicago,L=los,ST=Nord,C=PE",
"subjectAlternativeName": "DNS:httpsN-dep.san-dc.yu.com, DNS:test1.com, DNS:test.com"
},
"item.subjectAlternativeName.split(',') | map('regex_replace', '.*DNS:(.*)', 'DNS:\\\\1') | list | sort": [
"DNS:\\1",
"DNS:\\1",
"DNS:\\1"
]
}
When I use the same expression in a debug msg it works as expected.
ok: [localhost] => (item={'createDateTime': '2023-11-27T09:23:39.000Z', 'expirationDate': 1781379023, 'expirationDateTime': '2023-04-13T09:43:40.000Z', 'subject': 'CN=test.com,O=chicago,L=los,ST=Nord,C=PE', 'subjectAlternativeName': 'DNS:test.com, DNS:test1.com, DNS:httpsN-dep.san-dc.yu.com'}) => {
"msg": [
"DNS:httpsN-dep.san-dc.yu.com",
"DNS:test.com",
"DNS:test1.com"
]
}
How can I troubleshoot this condition ?
when: item.subjectAlternativeName.split(',') | map('regex_replace', '.*DNS:(.*)', 'DNS:\\1') | list | sort == alt_name

Iterate Over 2 dictionary in ansible

I Have 2 dictionary:
- Test1:
1: pass
2: fail
3: pass
- Test2:
1.1.1.1: val1
2.2.2.2: val2
3.3.3.3: val3
Condition is when Test1.value contians fail
- name: test
debug:
msg: "{{item.1.value}} {{item.1.key}} {{item.0.key}} {{item.0.value}}"
with_together:
- "{{Test1}}"
- "{{Test2}}"
when: item.0.value == "fail"
This is not working as expected unable to get both key and value of 2 dict in one loop
In when statement you must to use item.0 or item.1 to evaluate the condition. And I recommend you use a list in with_together loop and if you are using a variable you have to use braces {{ variable }} .
Try as below:
- name: test
debug:
msg: "{{item.1 }}"
with_together:
- "{{ Test1.values() | list }}"
- "{{ Test2.values() | list }}"
when: item.0 == "fail"
You'll get
TASK [test] *******************************************************************************************************************************************************************************************************
skipping: [127.0.0.1] => (item=['pass', 'val1'])
ok: [127.0.0.1] => (item=['fail', 'val2']) => {
"msg": "val2"
}
skipping: [127.0.0.1] => (item=['pass', 'val3'])
I achieved this by :
converting dict to list using filter -> |list
since
both dict of same size I was able to get data of both dict in single loop:
- name: test
debug:
msg: "{{item.0}} {{item.1}} {{item.2}} {{item.3}}"
with_together:
- "{{ Test1.values() | list }}"
- "{{ Test2.values() | list }}"
- "{{ Test1.keys() | list }}"
- "{{ Test2.keys() | list }}"
when: item.0 == "fail"

Debug of output in ansible fails with error

I am using following playbook
---
- name: ""
hosts: nexus
tasks:
- name: ""
debug:
var="{{ vlans | map(attribute='vlan_id') | join(',') }}"
and the output is:
ok: [nx01] => {
"10,20,100,30": "(10, 20, 100, 30)"
}
What is the type of my output ?
I tried
---
- name: ""
hosts: nexus
tasks:
- name: ""
debug:
var="{{ vlans | map(attribute='vlan_id') | join(',') | type_debug }} "
but get an error
ok: [nx02] => {
"str ": "VARIABLE IS NOT DEFINED!"
}
try this:
- name: Create a string with vlans
debug:
msg:
"{{ vlans | map(attribute = 'vlan_id') | join(',') }}"
- name: Create a string with vlans | type_debug
debug:
msg:
"{{ vlans | map(attribute = 'vlan_id') | join(',') | type_debug }}"
output:
TASK [Create a string with vlans] **************************************
ok: [localhost] => {
"msg": "10,20,100,30"
}
TASK [Create a string with vlans | type_debug] *************************
ok: [localhost] => {
"msg": "str"
}
The same example with list:
- name: Create a list with vlans
debug:
msg:
"{{ vlans | map(attribute = 'vlan_id') | list | default([]) }}"
- name: Create a string with vlans | type_debug
debug:
msg:
"{{ vlans | map(attribute = 'vlan_id') | list | default([]) | type_debug }}"
And the Output:
TASK [Create a list with vlans] ***************************************
ok: [localhost] => {
"msg": [
10,
20,
100,
30
]
}
TASK [Create a string with vlans | type_debug] ************************
ok: [localhost] => {
"msg": "list"
}
And if you need to set a variable:
- name: Set variable
set_fact:
mystrvar: "{{ vlans | map(attribute = 'vlan_id') | join(',') }}"
mylistvar: "{{ vlans | map(attribute = 'vlan_id') | list | default([]) }}"
- name: Debug mystrvar
debug:
var: mystrvar
- name: Debug mystrvar
debug:
var: mystrvar | type_debug
- name: Debug mylistvar
debug:
var: mylistvar
- name: Debug mylistvar
debug:
var: mylistvar | type_debug
The output:
TASK [Set variable] *******************************************************
ok: [localhost]
TASK [Debug mystrvar] *****************************************************
ok: [localhost] => {
"mystrvar": "10,20,100,30"
}
TASK [Debug mystrvar] *****************************************************
ok: [localhost] => {
"mystrvar | type_debug": "str"
}
TASK [Debug mylistvar] ****************************************************
ok: [localhost] => {
"mylistvar": [
10,
20,
100,
30
]
}
TASK [Debug mylistvar] ****************************************************
ok: [localhost] => {
"mylistvar | type_debug": "list"
}

How to create list for with_item from included list

I have this list:
"mylist": [
{ "files": [{"path": "path11"}, {"path": "path12"}] },
{ "files": [{"path": "path21"}, {"path": "path22"}] }
]
and I need to run role with with_items, where items should be path elements from my list.
For example:
- debug: msg="{{ item }}"
with_items: "{{ mylist | some_magic }}"
Needed output:
TASK [test : debug] **********************************
ok: [host] => (item=path11 ) => {
"msg": "path11"
}
ok: [host] => (item=path12 ) => {
"msg": "path12"
}
ok: [host] => (item=path21 ) => {
"msg": "path21"
}
...
Is it possible?
This is what I have already tried:
Constructions look like this:
- debug: msg="{{ item }}"
with_items: "{% for files in mylist | map(attribute='files') %}{% for file in files %}{{file.path}}{% endfor %}{% endfor %}"
Returned value as expected is not a list.
Wrong constructions look like this:
- debug: msg="{{ item }}"
with_items: "{{ mylist | map(attribute='files') | map(attribute='path') | list }}"
It is a legacy with_subelements loop pattern.
Using the loop keyword (Ansible 2.5 and later) you can iterate with:
- debug:
msg: "{{ item.1.path }}"
loop: "{{ mylist | subelements('files') }}"
Or the same using JMESPath (this shows how to create a list out of the whole data structure):
- debug:
msg: "{{ item }}"
loop: "{{ mylist | json_query('[].files[].path') }}"

Resources