Ansible - Multiple Loops - Directory Verification - ansible

Looking to create a list of directories for ansible to verify exist then check they are correctly defined and if not update them.
How can create multiple loops that work together?
# Determine if a path exists and is a directory.
- name: check directory existance and characteristics
stat: path=/path1
register: p1
# both that p.stat.isdir actually exists, and also that it's set to true.
- debug: msg="Path exists"
when: p1.stat.isdir is defined
- debug: msg="This is a directory"
when: p1.stat.isdir
- file: path=/path1 owner='user1' group='group1' mode=0755 state=directory
when: p1.stat.pw_name != 'user1' or p1.stat.gr_name != 'group1' or p1.stat.mode != '0755'
Ideally want to check all directories exist and update where they do then fail with list of those that don't.
Ultimately want something like file of directories and ownership settings to verify.

You can define a task.yml with below checks and then in a playbook execute the task with array of paths you need to run this task on.
----Task.yml
# Determine if a path exists and is a directory.
- name: check directory existance and characteristics
stat: path=/path1
register: p1
# both that p.stat.isdir actually exists, and also that it's set to true.
- debug: msg="Path exists"
when: p1.stat.isdir is defined
- debug: msg="This is a directory"
when: p1.stat.isdir
- file: path=/path1 owner='user1' group='group1' mode=0755 state=directory
when: p1.stat.pw_name != 'user1' or p1.stat.gr_name != 'group1' or p1.stat.mode != '0755'
playbook to run the task
--- Playbook
- name: Running Task
host: local
var:
- paths: ["path1" , "path2", "path3"]
tasks:
- include: task.yml path={{item}}
with_items: paths

Ideally want to check all directories exist and update where they do then fail with list of those that don't.
If that's the case then you're over-engineering things. All you need to do is invoke the file task with the appropriate parameters for each directory. Since Ansible is idempotent it will check the parameters for you and only change those which need to be changed. So something like this should be all you need:
- name: directories
file: path={{ item.p }}
state=directory
owner={{ item.o }}
group={{ item.g }}
mode=0755
with_items:
- { p: '/deploy', o: 'deploy_user', g: 'deploy_group' }
- { p: '/deploy/scripts', o: 'deploy_user', g: 'deploy_group' }
- { p: '/deploy/lib', o: 'deploy_user', g: 'deploy_group' }
The first time this task is run it will create the directories /deploy, /deploy/scripts, and /deploy/lib with the specified ownership & groups. The second time this task is run it should do nothing since those paths will already exist with the specified ownership & groups. Ansible will format the output nicely, especially if it's running in a shell with color enabled, so it will be easy to just read the output of this one task to determine what was changed and what wasn't.
Edit: If you want to test & display errors if directories don't exist then a simple two step approach should also work:
vars:
my_dirs:
- { p: '/deploy', o: 'deploy_user', g: 'deploy_group' }
- { p: '/deploy/scripts', o: 'deploy_user', g: 'deploy_group' }
- { p: '/deploy/lib', o: 'deploy_user', g: 'deploy_group' }
tasks:
- name: Check directories
stat: path={{ item.p }}
register: st
with_items: my_dirs
- name: Complain
debug: "Path {{ item.p }} does not exist or isn't set properly"
when: p1.stat.isdir is not defined or not p1.stat.isdir or p1.stat.pw_name != item.o or ...
with_items: my_dirs
- name: create directories
file: path={{ item.p }}
state=directory
owner={{ item.o }}
group={{ item.g }}
mode=0755
with_items: my_dirs

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}}"

detect file difference (change) with `ansible`

In this task I found a roundabout method to compare two files (dconfDump and dconfDumpLocalCurrent) and to set a variable (previously defined as false) to true if the two files differ.
The solution seem to work, but it looks ugly and, as a beginner with ansible, I have the impression a better solution should be existing.
---
# vars file for dconfLoad
local_changed : false
target_changed : false
---
- name: local changed is true when previous target different then local current
shell: diff /home/frank/dconfDump /home/frank/dconfDumpLocalCurrent
register: diff_oldtarget_localCurrent
register: local_changed
ignore_errors: true
- debug:
msg: CHANGED LOCALLY
when: local_changed
Some background to the task, which is an attempt to synchronize files: A file LocalCurrent is compared with LocalOld and CurrentTarget, to determine if the LocalCurrent is changed and if it is different than currentTarget. If LocalCurrent is not changed and CurrentTarget is changed, then apply the change (and set LocalOld to CurrentTarget); if LocalCurrent is changed then upload to controller.
What is the appropriate approach with ansible? Thank you for help!
You can use stat to get the checksum and then compare it. Please see below.
tasks:
- name: Stat of dconfDump
stat:
path : "/tmp/dconfDump"
register: dump
- name: SHA1 of dconfDump
set_fact:
dump_sha1: "{{ dump.stat.checksum }}"
- name: Stat of dconfDumpLocalCurrent
stat:
path : "/tmp/dconfDumpLocalCurrent"
register: dump_local
- name: SHA1 of dconfDumpLocalCurrent
set_fact:
local_sha1: "{{ dump_local.stat.checksum }}"
- name: Same
set_fact:
val: "False"
when: dump_sha1 != local_sha1
- name: Different
set_fact:
val: "True"
when: dump_sha1 == local_sha1
- name: Print
debug:
msg: "{{val}}"
Use stat and create dictionary of checksums. For example
- stat:
path: "{{ item }}"
loop:
- LocalOld
- LocalCurrent
- CurrentTarget
register: result
- set_fact:
my_files: "{{ dict(paths|zip(chkms)) }}"
vars:
paths: "{{ result.results|map(attribute='stat.path')|list }}"
chkms: "{{ result.results|map(attribute='stat.checksum')|list }}"
- debug:
var: my_files
gives (abridged) if all files are the same
my_files:
CurrentTarget: 7c73e9f589ca1f0a1372aa4cd6944feec459c4a8
LocalCurrent: 7c73e9f589ca1f0a1372aa4cd6944feec459c4a8
LocalOld: 7c73e9f589ca1f0a1372aa4cd6944feec459c4a8
Then use the dictionary to compare the checksums and copy files. For example
# If LocalCurrent is not changed and CurrentTarget is changed,
# then apply the change (and set LocalOld to CurrentTarget)
- debug:
msg: Set LocalOld to CurrentTarget
when:
- my_files['LocalCurrent'] == my_files['LocalOld']
- my_files['LocalCurrent'] != my_files['CurrentTarget']
- debug:
msg: Do not copy anything
when:
- my_files['LocalCurrent'] == my_files['LocalOld']
- my_files['LocalCurrent'] == my_files['CurrentTarget']
gives
TASK [debug] ****
skipping: [localhost]
TASK [debug] ****
ok: [localhost] =>
msg: Do not copy anything

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

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.

registering stat result with_items doing task if stat result exists

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.

Resources