In my playbook, I am trying to get list of sub-directory names using the find module and then extracting the basename from path. I have been able to get the list but the elements are prepended with u'. How can I remove those from the output?
Ansible version 2.9
I tried to look at these SO posts here and here, but couldn't get it to work.
I may not have fully understood how they should be applied
This is part of my playbook:
- name: set item.path | basename
set_fact: dir_name_list2_basename="{{ item.path | basename}}"
with_items: "{{ zookeeper_data_dir.files}}"
register: item_path_basename_list
- debug:
msg: "{{item_path_basename_list.results}}"
- name: debug item.path | basename as list
debug:
var: item.ansible_facts.dir_name_list2_basename
with_items: "{{item_path_basename_list.results}}"
- debug: msg="item_path_basename_list.results {{ item_path_basename_list.results | map(attribute='ansible_facts.dir_name_list2_basename') | list }}"
- name: set fact to array
set_fact: basename_array="{{ item_path_basename_list.results | map(attribute='ansible_facts.dir_name_list2_basename') | list }}"
- debug:
msg: "basename_array &&&&&&&& {{basename_array}}"
And this is the output of the last debug:
ok: [zk3-dev] => {
"msg": "basename_array &&&&&&&& [u'version-2_backup', u'version-2']"
}
ok: [zk2-dev] => {
"msg": "basename_array &&&&&&&& [u'version-2_backup', u'version-2']"
}
ok: [zk1-dev] => {
"msg": "basename_array &&&&&&&& [u'version-2_backup', u'version-2']"
}
I would like the basename_array to show up as ["version-2_backup", "version-2"] without the u prefix
How should I change my set fact to array task, so I will get the desired result?
Since ["version-2_backup", "version-2"] is actually a JSON array, you could use the to_json filter.
This said, your long set of tasks looks like an overcomplicated process for a requirement that can be achieved with the right set of map filters, since map can apply the same filter to all the elements of a list, you can easily fit your basename in it.
So, given:
- debug:
msg: >-
basename_array &&&&&&&&
{{
zookeeper_data_dir.files
| map(attribute='path')
| map('basename')
| to_json
}}
This yields:
ok: [localhost] => {
"msg": "basename_array &&&&&&&& [\"version-2_backup\", \"version-2\"]"
}
Note that the double quotes are escaped because you are using the JSON stdout callback. But, if you change the callback to YAML, this would yield exactly what you expected:
ok: [localhost] =>
msg: basename_array &&&&&&&& ["version-2_backup", "version-2"]
Related
Let me introduce my problem. I have some list of dictionary in my Ansible code:
my_example_list = [
{
"key1" : "value_of_first_key"
},
{
"key2": "value_of_second_key"
},
{
"key3": "value_of_third_key"
}
]
I need execute command which will iterate over this list and it should look something like:
- name: 'Example'
shell: 'Here is my {{ item.key }} and here is {{ item.value }}'
What I've do or try to do:
I was trying to do that with with_items but i'm not able to point into value of particular key.
I've also try to filter values using | first and | last but it's not worked in my case.
What I want to achieve:
Creating loop which will iterate via that list and inject separated key and value into command.
I was asked to show how I was trying to resolve my issue:
Here is some code:
# Showing last component failing
- name: "Try to show last component of my list"
debug:
msg: "{{ my_example_list[1] | last }}"
# When i'm trying to show first component of my list i get "key1"
- name: "Try to show first component of my list"
debug:
msg: "{{ my_example_list[1] | first }}"
# This shows me my list of dict
- name: "Trying use with_items"
debug:
msg: "{{ item }}"
with_items: "{{ my_example_list }}"
# But when i'm trying point to key and value for example
- name: "Trying use with_items point to key and value"
debug:
msg: "Here is my {{ item.key }} which keep {{ item.value }}"
with_items: "{{ my_example_list }}"
# It's failing.
Sorry it's not maybe solution with using loop. I'm just stack with that issue over few days... And as first step I want to know how correctly point to pair keys and values.
It also works well:
- name: Correct solution
debug:
msg: "This is my {{ item.key }} and my value {{ item.value }}"
with_dict: "{{ my_example_list }}"
Thanks #U880D for help! I'm not able to add some plus for your solution because I'm new joiner. Appreciate your answer! :)
Your data structure and naming seems to be unfavorable. There is no need to number the key name and therefore it should be avoided. Furthermore counting list elements in Python starts at 0 not 1.
The following minimal example playbook
---
- hosts: localhost
become: false
gather_facts: false
vars:
example_list: |
[
{
"key1" : "value_of_first_key"
},
{
"key2": "value_of_second_key"
},
{
"key3": "value_of_third_key"
}
]
tasks:
- name: Example loop
debug:
msg: "{{ item }} is of type {{ item | type_debug }}"
loop: "{{ example_list }}"
- name: Example loop
debug:
msg: "{{ item.values() }}"
loop: "{{ example_list }}"
will result into an output of
TASK [Example loop] ******************************************
ok: [localhost] => (item={u'key1': u'value_of_first_key'}) =>
msg: '{u''key1'': u''value_of_first_key''} is of type dict'
ok: [localhost] => (item={u'key2': u'value_of_second_key'}) =>
msg: '{u''key2'': u''value_of_second_key''} is of type dict'
ok: [localhost] => (item={u'key3': u'value_of_third_key'}) =>
msg: '{u''key3'': u''value_of_third_key''} is of type dict'
TASK [Example loop] ******************************************
ok: [localhost] => (item={u'key1': u'value_of_first_key'}) =>
msg:
- value_of_first_key
ok: [localhost] => (item={u'key2': u'value_of_second_key'}) =>
msg:
- value_of_second_key
ok: [localhost] => (item={u'key3': u'value_of_third_key'}) =>
msg:
- value_of_third_key
Further Readings
How to work with lists and dictionaries in Ansible
Extended loop variables
I have below output
msg: ' [(''N5K'', ''5548UPQ'')] '
I've tried the following
" {{ platform_list[0] }} "
but it returns one character only and I want to extract 'N5K'.
As already mentioned in the comments, the output is not a list but a string.
To get a better insight into the variable type you may use according Managing data type - Discovering the data type type_debug.
- debug:
msg: "{{ platform_list }} of type {{ platform_list| type_debug }}"
Further Q&A
Ansible - Check variable type
Ansible - Type cast
How to proceed further?
To get a better understanding you may have a look into the following Minimal, Reproducible Example
---
- hosts: localhost
become: false
gather_facts: false
vars:
LIST: ['N5K', '5548UPQ']
tasks:
- name: Show type
debug:
msg: "{{ LIST }} and of type {{ LIST | type_debug }}"
- name: Show element
debug:
msg: "{{ LIST[0] }}"
resulting into an output of
TASK [Show type] *********************************
ok: [localhost] =>
msg: '[u''N5K'', u''5548UPQ''] and of type list'
TASK [Show element] ******************************
ok: [localhost] =>
msg: N5K
You can then change the vars in example into a string
vars:
STRING: "['N5K', '5548UPQ']"
tasks:
- name: Show type
debug:
msg: "{{ STRING }} and of type {{ STRING | type_debug }}"
- name: Show element
debug:
msg: "{{ STRING[0] }}"
resulting into an output of
TASK [Show type] *****************************************
ok: [localhost] =>
msg: '[''N5K'', ''5548UPQ''] and of type AnsibleUnicode'
TASK [Show element] **************************************
ok: [localhost] =>
msg: '['
because of Python Slicing.
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.
One can loop like so:
- stat:
path: "{{ item }}"
loop:
- /tmp
- /home
Now, imagine this dict:
my_dict:
one:
path: /somewhere
file_one: filename.txt
file_two: whatever
some_var: 'not relevant'
two:
path: /foo
file_one: filename.txt
file_two: oui.nice
some_var: 'not relevant'
When I want to loop over a certain value in the dict:
- stat:
path: "{{ item.value.path }}"
loop: "{{ crt_to_gen | dict2items }}"
How would create a loop, so that it would stat the path, file_one and file_two for each key in the dict? I'm a bit lost here.
Since you can use + to concatenate lists, you could zip a list of paths to the attribute file_one and then concatenate it with the same for file_two, all those using the usual map.
Given the task:
- debug:
msg: "Path to file is `{{ item.0 }}/{{ item.1 }}`"
loop: >-
{{
_paths | zip(my_dict.values() | map(attribute='file_one')) | list +
_paths | zip(my_dict.values() | map(attribute='file_two')) | list
}}
vars:
_paths: "{{ my_dict.values() | map(attribute='path') }}"
my_dict:
one:
path: /somewhere
file_one: filename.txt
file_two: whatever
some_var: 'not relevant'
two:
path: /foo
file_one: filename.txt
file_two: oui.nice
some_var: 'not relevant'
This yields:
ok: [localhost] => (item=['/somewhere', 'filename.txt']) =>
msg: Path to file is `/somewhere/filename.txt`
ok: [localhost] => (item=['/foo', 'filename.txt']) =>
msg: Path to file is `/foo/filename.txt`
ok: [localhost] => (item=['/somewhere', 'whatever']) =>
msg: Path to file is `/somewhere/whatever`
ok: [localhost] => (item=['/foo', 'oui.nice']) =>
msg: Path to file is `/foo/oui.nice`
Note: I used the values() method of Python dictionaries, to change for the usual dict2items, but it could also be used if needed.
The only way I've found to select variables by a wildcard is to loop all variables and test match. For example
tasks:
- debug:
var: item
loop: "{{ query('dict', hostvars[inventory_hostname]) }}"
when: item.key is match("^.*_python_.*$")
shell> ansible-playbook test.yml | grep key:
key: ansible_python_interpreter
key: ansible_python_version
key: ansible_selinux_python_present
Is there a more efficient way to do it?
Neither json_query([?key=='name']), nor lookup('vars', 'name') work with wildcards.
Is there any other "wildcard-enabled" test, filter ...?
Note: regex_search is discussed in What is the syntax within the regex_search() to match against a variable?
You can select/reject with Jinja tests:
- debug:
msg: "{{ lookup('vars', item) }}"
loop: "{{ hostvars[inventory_hostname].keys() | select('match', '^.*_python_.*$') | list }}"
gives:
ok: [localhost] => (item=ansible_selinux_python_present) => {
"msg": false
}
ok: [localhost] => (item=ansible_python_version) => {
"msg": "2.7.10"
}
Update for Ansible 2.8 and higher versions.
There is the lookup plugin varnames added in Ansible 2.8. to "List of Python regex patterns to search for in variable names". See details
shell> ansible-doc -t lookup varnames
For example, to list the variables .*_python_.* the task below
- debug:
msg: "{{ item }}: {{ lookup('vars', item) }}"
loop: "{{ query('varnames', '^.*_python_.*$') }}"
gives
TASK [debug] ***************************************************************
ok: [localhost] => (item=ansible_python_interpreter) =>
msg: 'ansible_python_interpreter: /usr/bin/python3'
ok: [localhost] => (item=ansible_python_version) =>
msg: 'ansible_python_version: 3.8.5'
ok: [localhost] => (item=ansible_selinux_python_present) =>
msg: 'ansible_selinux_python_present: True
Moreover, the lookup plugin varnames will find also variables not 'instantiated' yet and therefore not included in the hostvars.