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
Related
I am trying to make a playbook that will check if something exists and depending on the results, it will execute a command.
I have simplified the problem but here is the gist of it :
I have a list, called "list" :
sample1
sample2
sample3
sample4
I will start by checking if a directory with this name exists.
- name: Status
shell: ls -l | grep {{ item }} | grep -v grep | wc -l
loop: "{{ list }}"
register: status
then i ll determine whether the folder exists or not (not sure if i need this step...)
- debug:
msg: {{ item.item }} exists
loop: "{{ status.results }}"
when: item.stdout != "0"
register: check
- debug:
msg: {{ item.item }} does not exist
loop: "{{ status.results }}"
when: item.stdout = "0"
register: check
the next step is where i am stuck... cant really find the right syntax or way to do this.. Anyway, i want to check if my folder exists or not, if it does not i want to create it.
- name: creation
shell: mkdir {{ item }}
loop: "{{ list }}"
when: check.results.item.stdout != "0"
as it need to check for every results from the list, my condition is based on the "check.results" and not the "list" defined in the loop.
I dont really know if this can be written as such
This is a very common misunderstanding. Ansible is about describing the expected state of the remote system and is generally idempotent for most of its modules. Running the same task an infinite number of times will lead to the same result on the target. In other words, don't check if a directory exists to later give the order to create it. Just describe the expected state: the directory must exist.
This can be done in a single task with the ansible.builtin.file module
- name: Make sure needed directories exist
ansible.builtin.file:
path: "{{ item }}"
state: directory
loop: "{{ list }}"
As the description name in my example suggests, this will create the needed dirs for those who do not exist reporting CHANGE and leave the others alone reporting OK.
Tip: whenever you are going to use shell hold a minute and check the documentation for a module doing the job. In most cases there is one. For example you can use the ansible.builtin.find module rather than looking for files with the shell.
In case you still want to apply a bad solution, the answer was already in my previous version of the answer and is also (again) in you debug task. You just have to apply the same receipe.
- name: creation
shell: mkdir {{ item.item }}
loop: "{{ status.results }}"
when: item.stdout != "0"
Note that this would work with a correct module as well but does not really make sense
- name: Make sure missing directories are created
ansible.builtin.file:
path: "{{ item.item }}"
state: directory
loop: "{{ status.results }}"
when: item.stdout != "0"
Both of these examples require of course to run your previous task (which should be refactored to a find module as stated earlier)
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.
I have a task to create a one-off cleanup playbook which is using variables from a role, but i don't need to execute that role. Is there a way to provide a role name to get everything from it's defaults and vars, without hardcoding paths to it? I also want to use vars defined in group_vars or host_vars with higher precedence than the ones included from role.
Example task:
- name: stop kafka and zookeeper services if they exist
service:
name: "{{ item }}"
state: stopped
with_items:
- "{{ kafka_service_name }}"
- "{{ zookeeper_service_name }}"
ignore_errors: true
where kafka_service_name and zookeeper_service_name are contained in role kafka, but may also be present in i.e. group_vars.
I came up with a fairly hacky solution, which looks like this:
- name: save old host_vars
set_fact:
old_host_vars: "{{ hostvars[inventory_hostname] }}"
- name: load kafka role variables
include_vars:
dir: "{{ item.root }}/{{ item.path }}"
vars:
params:
files:
- kafka
paths: "{{ ['roles'] + lookup('config', 'DEFAULT_ROLES_PATH') }}"
with_filetree: "{{ lookup('first_found', params) }}"
when: item.state == 'directory' and item.path in ['defaults', 'vars']
- name: stop kafka and zookeeper services if they exist
service:
name: "{{ item }}"
state: stopped
with_items:
- "{{ old_host_vars['kafka_service_name'] | default(kafka_service_name) }}"
- "{{ old_host_vars['zookeeper_service_name'] | default(zookeeper_service_name) }}"
include_vars task finds the first kafka role folder in ./roles and in default role locations, then includes files from directories defaults and vars, in correct order.
I had to save old hostvars due to include_vars having higher priority than anything but extra vars as per ansible doc, and then using included var only if old_host_vars returned nothing.
If you don't have a requirement to load group_vars - include vars works quite nice in one task and looks way better.
UPD: Here is the regexp that i used to replace vars with old_host_vars hack.
This was tested in vscode search/replace, but can be adjusted for any other editor
Search for vars that start with kafka_:
\{\{ (kafka_\w*) \}\}
Replace with:
{{ old_host_vars['$1'] | default($1) }}
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.
I'm using the module stat for checking if some files exists in a remote server and registering their result in a variable. i.e. config files I'm looking for: conf_dev.conf, conf_pred.conf, conf_pro.conf.
Later I'm using the copy module for transfering only the files that are missing using a conditional based on an attribute stored in
variable.results.{{(ITEM)index}}.stat.exists.
This returnsTRUE if file exists and FALSE if does not.
For running properly As the register variable is an array there are an index storing the result for each file I'm asking so I don't know how to convert the item in a index (0,1,2,3)
Does anyone know how to get the index of an item? I've tried this (look at the last line):
- name: Checking if common configuration files exists
stat:
path: "{{HOME_COMUN_CONFIG}}/{{item}}"
with_items: "{{LIST_COMMON_CONFIGURATION_ARTIFACTS}}"
register: store_results
- name: debug existe_app_comun
debug:
var: store_results
- name: Deploying missing files
copy:
src: "{{DIRTEMP_COMUN_CONFIG}}/{{item}}"
dest: "{{HOME_COMUN_CONFIG}}/{{item}}"
with_items: "{{LIST_COMMON_CONFIGURATION_ARTIFACTS}}"
when: existe_app_comun.results.{{index(item)}}.stat.exists is defined
Why do you loop over LIST_COMMON_CONFIGURATION_ARTIFACTS if you want to loop over registered variable?
- name: Deploying missing files
copy:
src: "{{DIRTEMP_COMUN_CONFIG}}/{{ item.item }}"
dest: "{{HOME_COMUN_CONFIG}}/{{ item.item }}"
with_items: "{{ existe_app_comun.results }}"
when: item.stat.exists
Here item is an element of results and item.item is an element of original loop.
P.S. if files at destination folder are not modified and should be same as in DIRTEMP_COMUN_CONFIG, then you must not use stat+copy, but just use copy – because it's idempotent and will not copy the same file twice.