Ansible: find file and loop over paths - ansible

Using an Ansible role. I would like to loop over a list of file paths, but I get an error:
template error while templating string: unexpected '/'.
String: {{/home/xyz/download.log}}
This is the main.yml for the "list_log_files" role:
- name: "find logs"
find:
paths: /
patterns: 'download.log'
recurse: yes
register: find_logs
- name: "list log files"
debug: var="{{ item.path }}"
with_items: "{{ find_logs.files }}"
The find returns an array "files", each is a dictionary. The dictionary contains a path entry, which is what I am interested in.

I have faced same issue and above issue is same where I want list of path of each file to insert line. I use Jinja2 filter:
- name: fetch files
find: paths=/var/tmp/ patterns='*.log'
register: find_logs
- name: insert line
lineinfile: dest={{ item }} line='my line' insertafter=EOF
with_items: "{{ find_logs.files | map(attribute='path') | list }}"
{{ find_logs.files | map(attribute='path') | list }}
Helpful Link
map()
Applies a filter on a sequence of objects or looks up an attribute.
This is useful when dealing with lists of objects but you are really
only interested in a certain value of it.

The correct syntax for var argument of debug module (with the value for your use case) is:
In Ansible notation:
debug: var=item.path
In YAML notation:
debug:
var: item.path
Ansible modules' usage is fairy well documented and examples cover most users' needs. This is also true for the debug module, so refer to the examples to check the basic syntax.

Related

Ansible - multiple items in path, but cannot use loop

I'm not sure how to describe the title or my question properly, feel free to edit.
I'll jump right in. I have this working piece of Ansible code:
- file:
path: "{{ item.item.value.my_folder }}/{{ item.item.value.filename }}"
state: absent
loop: "{{ my_stat.results }}"
when: item.stat is defined and item.stat.exists and item.stat.islnk
If Ansible is run, the task is executed properly, and the file is removed from the system.
Now, the issue. What I want Ansible to do is loop over multiple items described in "path". This way I won't have to create a seperate task for each filename I want to be deleted.
Example:
- file:
path:
- "{{ item.item.value.my_folder }}/{{ item.item.value.filename }}"
- "{{ item.item.value.my_folder }}/{{ item.item.value.other_filename }}"
state: absent
loop: "{{ my_stat.results }}"
when: item.stat is defined and item.stat.exists and item.stat.islnk
But Ansible doesn't proces the items in the list described in 'path', so the filesnames will not be deleted.
I see I cannot use 'loop', since it is already in use for another value.
Question: How would I configure Ansible so that I can have multiple items in the path and let Ansible delete the filenames, and keeping the current loop intact.
-- EDIT --
Output of the tasks:
I've removed the pastebin url since I believe it has no added value for the question, and the answer has been given.
As described in the documentation, path is of type path, so Ansible will only accept a valid path in there, not a list.
What you can do, though, is to slightly modify your loop and make a product between your existing list and a list of the filenames properties you want to remove, then use those as the key to access item.item.value (or item.0.item.value now, since we have the product filter applied).
For example:
- file:
path: "{{ item.0.item.value.my_folder }}/{{ item.0.item.value[item.1] }}"
state: absent
loop: "{{ my_stat.results | product(['filename', 'other_filename']) }}"
when:
- item.0.stat is defined
- item.0.stat.exists
- item.0.stat.islnk
PS: a list in a when is the same as adding and statements in the said when

ansible access results and file paths in nested loop

I am working on small playbook that will search for files in specified directories and then delete them if they met certain conditions. I have following playbook so far
- hosts:
- localhost
gather_facts: false
tasks:
- name: find logs
find:
paths: "{{ item.0 }}"
file_type: file
patterns: "{{ item.1 }}"
register: find_logs
with_nested:
- ["/var/log/apache2", "/var/log/nginx"]
- ["access.log", "error.log"]
- debug:
var: item
loop:
- "{{ find_logs }}"
So this will obviously look into /var/log/apache2 and /var/log/nginx directories and search for access.log and error.log files. Now, following ansible's documentation I want to access files return values and their paths. The issue I'm having right now is with nested loop and registered find_logs variable which holds list of dictionaries in results key. If I do find_logs.results then I will get a list of dictionaries and each of these dictionaries will have another list of files kept in files section. How can I 'flatten' this list even more to be able to retrieve files.path for every element produced by nested loop? To be honest I also tried find_logs | json_query('results[*].files[*]') but that gives me another list and I can't seem to iterate over it to get what I want (which is path of file). Any idea on how to make this work?
I completely misunderstood documentation for find module, Nested loop can be excluded in this case and replaced with following syntax
- hosts:
- localhost
gather_facts: false
tasks:
- name: find logs
find:
paths:
- /var/log/apache2
- /var/log/nginx
file_type: file
patterns:
- "access.log"
- "error.log"
- "other_vhosts_access.log"
register: find_logs
- name: check what was registered
debug:
msg: "my path -> {{ item.path }}"
with_items:
- "{{ find_logs.files }}"

How can I nest a with_filetree in Ansible?

I'm using Ansible to push config files for various apps (based on group_names) & need to loop thru the config .j2 templates from a list variable. If I use a known list of config templates I can use a standard with_nested like this...
template:
src: '{{ playbook_dir }}/templates/{{ item[1] }}/configs/{{ item[0] }}.j2'
dest: /path/to/{{ item[1] }}/configs/{{ item[0] }}
with_nested:
- ['file.1', 'file.2', 'file.3', 'file.4']
- '{{ group_names }}'
However, since each app will have its own configs I can't use a common list for a with_nested. Every attempt to somehow use with_filetree nested fails. Is there any way to nest a with_filetree? Am I missing something painfully obvious?
The most straightforward way to deal with this is probably to imbricate loops through an include. I take for granted that your app directory only contains .j2 files. Adapt if this is not the case.
In e.g. push_templates.yml
---
- name: Copy templates for group {{ current_group }}
template:
src: "{{ item.src }}"
dest: /path/to/{{ current_group }}/configs/{{ (item.src | splitext).0 | basename }}
with_filetree: "{{ playbook_dir }}/templates/{{ current_group }}"
# Or using the latest loop syntax
# loop: "{{ query('filetree', playbook_dir + '/templates/' + current_group) }}"
when: item.src is defined
Note: on the dest line, I am removing the last found extension of the file and getting its name only without the leading directory path. Check the ansible doc on filters for splitext and basename for more info
Then in your e.g. main.yml
- name: Copy templates for all groups
include_tasks: push_templates.yml
loop: "{{ group_names }}"
loop_control:
loop_var: current_group
Note the loop_var in the control section to disambiguate the possible item overlap in the included file. The var name is of course aligned with the one I used in the above included file. See the ansible loops documentation for more info.
An alternative approach to the above would be to construct your own data structure looping over your groups with set_fact and calling the filetree lookup on each iteration (see example above with the newer loop syntax), then loop over your custom data structure to do the job.

Ansible unable to calculate the checksum error

Hoping with some help on the following. I'm trying to make reference to an xml file within the work_dir location:
---
- include_vars:
file: var_file.yml
- name: Locate audit results file
find:
paths: "{{ item }}"
recurse: no
patterns: '*.xml'
with_items: "{{ work_dir }}"
register: audit_file
- name: Copy audit results file to local destination
fetch:
src: "{{ item }}/{{ audit_file }}"
dest: /home/bob/audit_results/
flat: yes
validate_checksum: no
with_items: "{{ work_dir }}"
var_file.yml:
---
work_dir:
- /var/tmp/audit
However the above code keeps erroring with:
"msg": "unable to calculate the checksum of the remote file"}
You find files on a loop of paths and register the result. Therefore your registered var audit_file contains a results attribute which is a list.
Each element of the results list contains a files attribute which is again a list. Each files element contains a result of your find run with all the info about the found file. In those info, there is a path attribute pointing to the exact path of the file on the remote server.
Before going further, I strongly suggest your read the above documentation and issue a debug of your var to understand its structure and content
- debug:
var: audit_file
If you want to fetch each found file, you need to make a loop on each results with a sub-loop on each files element. This can be done with a subelements lookup although we will only use here the sub element (i.e. item.1) of the loop.
This is how you can fix your second task:
- name: Copy audit results file to local destination
fetch:
src: "{{ item.1.path }}"
dest: /tmp/test/
flat: yes
validate_checksum: no
with_subelements:
- "{{ audit_file.results }}"
- files
Note: this is not the only solution, I went to the easiest and most obvious.

Iterating over stdout

I am writing a playbook to locate a string pattern in a sequence of files. If I run my utility through the command module it will generate one or more strings on STDOUT. To run this across a number of systems I would like to run the command with_items:
- command: "findstring {{ item }}"
with_items:
- "string1"
- "string2"
register: found
failed_when: found.rc >= 2
And then iterate over the result to post process the info:
- name: Print strings we found
debug:
var: "{{ item }}"
with_items: found.results
Is there something equivalent to loop.index that can be used with "results" in the task above? This would allow me to do something like {{ item[INDEX].stdout }} to get the strings that were generated. I haven't been able to find an answer in the official documentation so I thought I would post here to see what the gurus think.
If you need to iterate over every line from all commands, use:
- debug:
msg: "Do smth for line {{ item }}"
with_items: "{{ found | json_query('results[].stdout_lines[]') }}"
This will take ever element from found.results, then every element from every stdout_lines.
For me this works in ansible [core 2.11.6]:
- name: Command which outputs multiple lines
ansible.builtin.command:
cmd: ls -l /
register: _ls_cmd
changed_when: no
- name: Debug to show each line
ansible.builtin.debug:
msg: "ITEM: {{ item }}"
with_items: "{{ _ls_cmd.stdout_lines }}"

Resources