registering stat result with_items doing task if stat result exists - ansible

I am new to ansible and have exhausted my forum searches. I cannot seem to find an answer for this issue I am having with with_items and when. The playbook as it is now will run, but it results in failed messages "src file does not exist" for every path in the list that does not exist on that machine.
Since, this is being run against several machines, that's a lot of failed (red) messages that mean nothing. I thought the when statement would only run the task IF the statresult existed. This is not the case.
Basically what I am trying to do is check several machines to see if these two paths exist. If they do, create a symlink for each one. All the paths to check in are different. Right now I have:
---
- hosts: all
become: yes
gather_facts: no
tasks:
- name: Check that domains exist
stat:
path: '/path/to/the/domain/{{ item.domainpath }}'
get_attributes: no
get_checksum: no
get_md5: no
get_mime: no
register: item.statresult
with_items:
- { domainpath: 'path1/', statresult: 'stat_result_path1' }
- { domainpath: 'path2/', statresult: 'stat_result_path2' }
- { domainpath: 'path3/', statresult: 'stat_result_path3' }
- name: Create symlink for bin on existing domain machines
file:
src: '/path/to/the/domain/{{ item.srcbin }}'
dest: /path/new/symlink_bin_link
state: link
with_items:
- { srcbin: 'path1/bin/', domainexists: 'stat_result_path1.stat.exists' }
- { srcbin: 'path2/bin/', domainexists: 'stat_result_path2.stat.exists' }
- { srcbin: 'path3/bin/', domainexists: 'stat_result_path3.stat.exists' }
when: item.domainexists
ignore_errors: yes
- name: Create symlink for config on existing domain machines
file:
src: '/path/to/the/domain/{{ item.srcconfig }}'
dest: /path/new/symlink_config_link
state: link
with_items:
- { srcconfig: 'path1/config/', domainexists: 'stat_result_path1.stat.exists' }
- { srcconfig: 'path2/config/', domainexists: 'stat_result_path2.stat.exists' }
- { srcconfig: 'path3/config/', domainexists: 'stat_result_path3.stat.exists' }
when: item.domainexists
ignore_errors: yes
I have to use ignore_errors because otherwise it will not go to the second task. I have tried to use when: item.domainexists == true but that results in the task getting skipped even when it matches a path that exists.
Even if the when statement iterates over every with_items, it should not matter because as long as it matches one, it should do the task correctly?

This is how your playbook should look like:
---
- hosts: all
become: yes
gather_facts: no
tasks:
- name: Check that domains exist
stat:
path: /path/to/the/domain/{{ item }}
loop:
- path1
- path2
- path3
register: my_stat
- name: Ensure symlinks are created for bin on existing domain machines
file:
src: /path/new/symlink_bin_link
dest: /path/to/the/domain/{{ item }}/bin
state: link
loop: "{{ my_stat.results | selectattr('stat.exists') | map(attribute='item') | list }}"
- name: Ensure symlinks are created for config on existing domain machines
file:
src: /path/new/symlink_config_link
dest: /path/to/the/domain/{{ item }}/config
state: link
loop: "{{ my_stat.results | selectattr('stat.exists') | map(attribute='item') | list }}"
Explanation:
register: item.statresult is a nonsensical construct in Ansible, you should provide a name of a variable as a parameter
that variable will contain a list of results for any task running in a loop
you should process that list (see this answer to learn about selectattr and map) to get a list of the paths which exist (only)
you should loop over that filtered-and-mapped list
Also: src and dest should be defined the other way round for symlinks than in your code.
Further, you can combine the last two tasks into one by adding a product filter to the iterable definition.

Related

How do I check a list of directories from a file, but some entries have multiple directories separated by a comma

I have a text file that looks like this:
hints_directory: /opt/cassandra/hints
data_file_directory: /opt/cassandra/data,/opt/cassandra/data2
commitlog_directory: /opt/cassandra/commitlog
cdc_raw_directory: /opt/cassandra/cdc_raw
saved_caches_directory: /opt/cassandra/saved_caches
tmp_directory: /opt/cassandra/tmp
jna_directory: /opt/cassandra/jna
dump_directory: /opt/cassandra/dump
I want to read that file and check for the existence of each directory using ansible. The tricky part comes on the second line, where the line contains 2 directory paths. How do I get ansible to read each line of the file as in the example below and check for the existence of each directory.
Here is what I have thus far, but it stumbles on the data_file_directory due to having multiple path values for the single line. I've tried to split the line entry, but that doesn't solve the issue.
Thoughts?
- name: Ensure the directories exist
file:
path: "{{item.split(':')[1].split(' ')[1].split(',')}}"
state: directory
with_lines: cat <<pathtofile>>/directories.txt
Read the variables from the file into a dictionary and convert the strings to lists, e.g.
- include_vars:
file: directories.txt
name: dirs_str
- set_fact:
dirs_list: "{{ dict(_keys|zip(_vals)) }}"
vars:
_keys: "{{ dirs_str.keys()|list }}"
_vals: "{{ dirs_str.values()|map('split', ',')|list }}"
gives
dirs_list:
cdc_raw_directory:
- /opt/cassandra/cdc_raw
commitlog_directory:
- /opt/cassandra/commitlog
data_file_directory:
- /opt/cassandra/data
- /opt/cassandra/data2
dump_directory:
- /opt/cassandra/dump
hints_directory:
- /opt/cassandra/hints
jna_directory:
- /opt/cassandra/jna
saved_caches_directory:
- /opt/cassandra/saved_caches
tmp_directory:
- /opt/cassandra/tmp
Select and flatten the values, e.g.
- set_fact:
allDirectories: "{{ dirs_list.values()|flatten }}"
gives
allDirectories:
- /opt/cassandra/hints
- /opt/cassandra/data
- /opt/cassandra/data2
- /opt/cassandra/commitlog
- /opt/cassandra/cdc_raw
- /opt/cassandra/saved_caches
- /opt/cassandra/tmp
- /opt/cassandra/jna
- /opt/cassandra/dump
, or use the expression directly in the loop
- name: Ensure the directories exist
file:
path: "{{ item }}"
state: directory
loop: "{{ dirs_list.values()|flatten }}"
For anyone else that may have a similar exercise, I have figured out how to do this:
- set_fact:
allDirectories: []
- set_fact:
allDirectories: "{{ item.split(':')[1].split(' ')[1].split(',') + allDirectories}}"
with_lines: cat <<pathtofile>>/directories.txt
- name: Ensure the directories exist
file:
path: "{{item}}"
state: directory
with_items: "{{allDirectories}}"

How to create directory and file inside the same directory in ansible using single task

Is there any way to create directory and file inside the same directory in ansible using a single task? Currently, in my task, I'm creating a directory using a file module, state= directory. How to touch a file inside that directory in the same task?
You could use a loop:
- name: Create a directory with a file inside
file:
path: "{{ item.path }}"
state: "{{ item.state }}"
loop:
- { path: /tmp/foo, state: directory }
- { path: /tmp/foo/bar.txt, state: touch }
#Dom H has given it correctly i just wanted it to give it more convenient way in playbook.
the playbook will look like :
---
- name: Creating directory and files
hosts: localhost
become: yes
become_user: root
tasks:
- name: Create a directory with a file inside
file:
path: "{{ item.path }}"
state: "{{ item.state }}"
loop:
- { path: /tmp/foo, state: directory }
- { path: /tmp/foo/bar.txt, state: touch }
and we can actually test before run it using:
ansible-playbook -i localhost mkdir.yml --check

Ansible find module giving error "does not seem to be a valid directory or it cannot be accessed" absolute path

Ansible find module isn't working as expected.
So i have three instances
One is test node , second controller node and third is from where i am running my ansible playbook
I am trying to generate ssh-keys on test_nodes and then fetching the public keys from those nodes. This is working fine.
Then I am trying to appending these public keys in the authorized_keys file of a different host(controller_node). For this, I am using the find module to get list of files and then loop over these files in authorized_key module.
I was using :
- name: Set authorized key file taken from file
authorized_key:
user: absrivastava
key: "{{ lookup('file','item') }}"
state: present
#with_file:
- "/home/absrivastava/ANSIBLE/ssh-keys/*/home/ribbon/.ssh/id_rsa.pub" This didnt work
#with_filetree:
- "/home/absrivastava/ANSIBLE/ssh-keys/*/home/ribbon/.ssh/id_rsa.pub" This was not appending data
But it didnt seem to work. So i am using find to get list of files and then iterate over them.
- name: Generate ssh keys
hosts: media_nodes
gather_facts: false
tasks:
- name: key generation
openssh_keypair:
path: ~/.ssh/id_ssh_rsa
force: True
register: public_key
- debug:
var: public_key.public_key
- name: fetch public key from all nodes
fetch:
src: ~/.ssh/id_ssh_rsa.pub
dest: ssh-keys/
- name: Controller play
hosts: controller
gather_facts: false
tasks:
- name: Find list of public key files
find:
paths: /home/abhilasha/ANSIBLE/ssh-keys/
file_type: file
recurse: yes
patterns: ".*pub"
use_regex: yes
register: files_matched
- name: debug files matched
debug:
var: files_matched.files
- name: Debug files_matched loop
debug:
var: item.path
loop: "{{ files_matched.files|flatten(levels=1) }}"
loop_control:
label: "{{ item.path }}"
- name: Set authorized key file taken from file
authorized_key:
key: "{{ lookup('file','item') }}"
state: present
with_file:
- "{{ files_matched.files }}"
- name: Find list of public key files
This play is not working giving error
TASK [Find list of public keys] *****************************************************************************************************************************************************************************************************************
ok: [test_controller] => {"changed": false, "examined": 0, "files": [], "matched": 0, "msg": "/home/abhilasha/ANSIBLE/ssh-keys/ was skipped as it does not seem to be a valid directory or it cannot be accessed\n"}
Okay so i got the issue , i was using hosts: controller for this play but the files are on my test VM instance .
But I am not sure how to still solve my problem. I want to use publoc keys on my local and then append it to controller server
- name: Fetch public key files from localhost
gather_facts: false
hosts: 127.0.0.1
connection: local
tasks:
- name: Find list of public keys
find:
paths: ssh-keys/
file_type: file
recurse: yes
patterns: "pub"
use_regex: yes
hidden: yes
register: files_matched
- name: Debug files_matched loop
debug:
var: item.path
loop: "{{ files_matched.files|flatten(levels=1) }}"
loop_control:
label: "{{ item.path }}"
- name: Add Public keys to controller authorized keys
hosts: controller
gather_facts: false
tasks:
- name: Set authorized key file taken from file
authorized_key:
key: "{{ lookup('file','item') }}"
state: present
with_file:
- "{{ files_matched.files }}"
I am unable to use files_matched variable outside the scope of that play. How can i make this work. Thanks in advance
Q: "msg": "/home/abhilasha/ANSIBLE/ssh-keys/ was skipped as it does not seem to be a valid directory or it cannot be accessed\n"
A: Take a look at the directory ssh-keys/ at controller and check the content. Instead of
paths: /home/abhilasha/ANSIBLE/ssh-keys/
find it in the same path
path: ssh-keys/
it has been fetch to
dest: ssh-keys/
Can you change the paths as below and try
- name: fetch public key from all nodes
fetch:
src: ~/.ssh/id_ssh_rsa.pub
dest: /tmp/ssh-keys/
- name: Find list of public key files
find:
paths: /tmp/ssh-keys/
file_type: file
recurse: yes
patterns: ".*pub"
use_regex: yes
register: files_matched
If you are trying to copy/find files from your local machine to the remote, by default the find module will run on the remote, not your local machine. As a result, the error will be thrown if those directories don't exist on your remote.
So you just tell it to "find" on your local machine by specifying delegate_to: localhost and it should work.
tasks:
- find:
paths:
- local_dir1
- local_dir2
file_type: file
patterns: '*.tgz'
register: files_output
# Execute task on this host instead of the target (inventory_hostname).
delegate_to: localhost
- block:
- set_fact:
files: "{{ files_output.files | map(attribute='path') }}"
- set_fact:
files: '{{ files + more }}'
vars:
more:
- '{{playbook_dir}}/remote.sh'
- debug:
msg: '{{ item }}'
loop: '{{ files }}'

Can't copy files using loop

I'm trying to copy many files using ansible.
this is my playbook :
- name: Copy the scenario test
copy:
src: files/{{ scenario_name }}
dest: /home/{{ user }}/scenario_creation
mode: '0644'
run_once: true
loop: "{{ scenario_name }}"
tags:
- user
- scenario
and this is my roles/scenario_test/defaults/main.yml
scenario_name: ['topup-scenario.json', 'test.json']
when I execute my playbook it says:
"msg": "Could not find or access 'files/[u'topup-scenario.json', u'test.json']'\nSearched in:\n\t/home/path/ansible/plays/files/[u'topup-scenario.json', u'test.json']\n\t/home/path/ansible/plays/files/[u'topup-scenario.json', u'test.json'] on the Ansible Controller.\nIf you are using a module and expect the file to exist on the remote, see the remote_src option"
}
any help ?
Change:
src: files/
to
src: ./files/
You need to change your code to this:
- name: Copy the scenario test
copy:
src: files/{{ item }}
dest: /home/{{ user }}/scenario_creation
mode: '0644'
run_once: true
loop: "{{ scenario_name }}"
tags:
- user
- scenario
The loop iterates the list to the term 'item', unless you redefine it with the loop_var option. So when you call scenario_name in your src line, you are actually calling the entire list, not an iteration of it.

How to create the multiple symlinks for the folders/files under the same source

I want to create the folder: temp2 which is able to store all the symlinks of the subfolders/files of other foder: temp1. with_items can help complete this task, but it needs to list down all the folder/file name as below script shown:
- name: "create folder: temp2 to store symlinks"
file:
path: "/etc/temp2"
state: directory
- name: "create symlinks & store in temp2"
file:
src: "/etc/temp1/{{ item.src }}"
dest: "/etc/temp2/{{ item.dest }}"
state: link
force: yes
with_items:
- { src: 'BEAM', dest: 'BEAM' }
- { src: 'cfg', dest: 'cfg' }
- { src: 'Core', dest: 'Core' }
- { src: 'Data', dest: 'Data' }
It is not flexible as the subfolders/files under temp1 would be added or removed, and I need to update above script frequently to keep the symlinks as updated
Is there any way to detect all the files/folder under temp1 automatically instead of maintaining the with_items list?
The following code works under Ansible-2.8:
- name: Find all files in ~/commands
find:
paths: ~/commands
register: find
- name: Create symlinks to /usr/local/bin
become: True
file:
src: "{{ item.path }}"
path: "/usr/local/bin/{{ item.path | basename }}"
state: link
with_items: "{{ find.files }}"
You can create a list of files using find module:
Return a list of files based on specific criteria. Multiple criteria are AND’d together.
You'll likely need to leave recurse set to false (default) since you assume subfolders might exist.
You need to register the results of the module with register declaration:
register: find
In the next step you need to iterate over the files list from the results:
with_items: "{{ find.results.files }}"
and refer to the value of the path key. You already know how to do it.
You will also need to extract the filename from the path, so that you can append it to the destination path. Use basename filter for that.

Resources