Ansible - replace particular string in multiple files if file exists - ansible

I can replace a string in the file if the file exists in a directory.
Similarly, I want to replace the string in another 25 files (test2.conf .. test25.conf) if the files exist in the same directory. Using with_items I can able to replace a string in all files but unable to use the register to check the file exists in the loop.
Below playbook is a success for one file, how can I achieve the same for 25 files?
- hosts: webserver
vars:
strings:
- user1
- user2
tasks:
- name: Check /home/user/test1.conf exists
stat:
path: /home/user/test1.conf
register: stat_result
- name: Replace string in test1.conf
replace:
dest: /home/user/test1.conf
backup: yes
regexp: '^Hello'
replace: "Hello {{ strings | join(' ')}}"
when: stat_result.stat.exists == True

Use find module instead of stat and iterate over the results:
- hosts: webserver
vars:
strings:
- user1
- user2
tasks:
- name: Get a list of test*.conf in /home/user
find:
paths: /home/user
patterns: test*.conf
register: my_find
- name: Replace strings in files found
replace:
dest: "{{ item.path }}"
backup: yes
regexp: '^Hello'
replace: "Hello {{ strings | join(' ')}}"
with_items: "{{ my_find.files }}"
Your replace task is not idempotent, it will add strings infinitely on subsequent runs.

Related

Ansible trim string

I'm trying write role to install MySql 8, and get problem with this:
- name: Extract root password from logs into {{ mysql_root_old_password }} variable
ansible.builtin.slurp:
src: "{{ mysql_logfile_path }}"
register: mysql_root_old_password
#when: "'mysql' in ansible_facts.packages"
- name: Extract root password from logs into {{ mysql_root_old_password }} variable
set_fact:
mysql_root_old_password: "{{ mysql_root_old_password.content | b64decode | regex_findall('generated for root#localhost: (.*)$', 'multiline=true') }}"
#when: "'mysqld' in ansible_facts.packages"
- name: Get Server template
ansible.builtin.template:
src: "{{ item.name }}.j2"
dest: "{{ item.path }}"
loop:
- { name: "my.cnf", path: "/root/.my.cnf" }
notify:
- Restart mysqld
on the .my.cnf I get password with quotes and brackets:
[client]
user=root
password=['th6k(gZeJSt4']
How to trim that?
What I try:
- name: trim password
set_fact:
mysql_root_old_password2: "{{ mysql_root_old_password | regex_findall('[a-zA-Z0-9,()!##$%^&*]{12}')}}"
Thanks.
The result of regex_findall is a list because there might be more matches. Take the last item
- set_fact:
mysql_root_old_password: "{{ mysql_root_old_password.content|
b64decode|
regex_findall('generated for root#localhost: (.*)$', 'multiline=true')|
last }}"
From your description
on the .my.cnf I get password with quotes and brackets ... How to trim that
I understand that you like to read a INI file like my.cnf.ini
[client]
user=root
password=['A1234567890B']
where the value of the key password looks like a list with one element in YAML and the structure doesn't change, but you are interested in the value without leading and trailing square brackets and single quotes only.
To do so there are several possibilities.
Via Ansible Lookup plugins
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Extract root password from INI file
debug:
msg: "{{ lookup('ini', 'password section=client file=my.cnf.ini') }}"
register: result
- name: Show result with type
debug:
msg:
- "{{ result.msg }}"
- "result.msg is of type {{ result.msg | type_debug }}"
- "Show password only {{ result.msg[0] }}" # the first list element
Such approach will work on the Control Node.
Like all templating, lookups execute and are evaluated on the Ansible control machine.
Further Q&A
How to read a line from a file into an Ansible variable
What is the difference between .ini and .conf?
Further Documentation
ini lookup – read data from an INI file
Via Ansible shell module, sed and cut.
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Extract root password from INI file
shell:
cmd: echo $(sed -n 's/^password=//p' my.cnf.ini | cut -d "'" -f 2)
register: result
- name: Show result
debug:
msg: "{{ result.stdout }}"
Please take note regarding "I get password with quotes and brackets ... ['<pass>'] ... How to trim that?" that from perspective of the SQL service, .cnf file, [' and '] are actually part of the password!
- name: Server template
template:
src: "my.cnf.ini"
dest: "/root/.my.cnf"
If that is correct they will be necessary for proper function of the service.

How to add multiple lines in all files present in a directory using Ansible

In Ansible script, First I'm using find_module to find all files in a directory, and then I'm using set_fact to mention all commands that I want to add in all files and then I am using lineinfile module to add multiple lines in all the files, but it is adding all commands in list format ['line1','line2','line3'] instead of this I want these lines to be added one after another in all files.
Below mentioned is the script
tasks:
- name: finding all files present in something directory
find:
paths: /etc/something.d/
file_type: file
patterns: '*.d'
register: c1
become: true
- set_fact:
lines:
- "line1"
- "line2"
- "line3"
- lineinfile:
path: "{{ item.path }}"
line: "{{ lines}}"
state: present
create: yes
backup: yes
register: c2
become: true
with_items: "{{ c1.files }}"
- debug:
var: c1
- debug:
var: c2
This works for me:
tasks:
- name: finding all files present in something directory
find:
paths: /etc/something.d/
file_type: file
patterns: '*.d'
register: c1
become: true
- blockinfile:
path: "{{ item.path }}"
state: present
create: yes
backup: yes
block: |
line1
line1
line1
register: c2
become: true
with_items: "{{ c1.files }}"
- debug:
var: c1
- debug:
var: c2
So... the old way, which I prefer, is with_nested:
set_fact:
lines:
- line1
- line2
- line3
lineinfile:
...
...
with_nested:
- "{{ c1.files }}"
- "{{ lines }}"
The new way, and I have no idea what escapee from a Ph.D. CS program came up with this, is to combine the lists:
set_fact:
lines:
- line1
- line2
- line3
lineinfile:
...
...
loop: "{{ c1.files | product | lines | list }}"
https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#with-nested-with-cartesian
If all the lines are together, you might want to look at blockinfile.
I was able to add multiple lines in all files present in a directory by using below mentioned ansible script, However the problem now is all lines are getting duplicated, if I run this script again, Please suggest any way so that lines won't get duplicated in all files
tasks:
- name: finding all files present in something directory
find:
paths: /etc/something.d/
file_type: file
patterns: '*.d'
register: c1
become: true
- set_fact:
lines:
- "line1"
- "line2"
- "line3"
- lineinfile:
path: "{{ item.path }}"
line: "{{ lines[0]+'\n'+lines[1]+'\n'+lines[2]+'\n'}}"
state: present
create: yes
backup: yes
register: c2
become: true
with_items: "{{ c1.files }}"
- debug:
var: c1
- debug:
var: c2

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

Ansible delete Files with wildcard/regex/glob with exception

I want to delete files based on a wildcard but also add exceptions to the rule.
- hosts: all
tasks:
- name: Ansible delete file wildcard
find:
paths: /etc/wild_card/example
patterns: "*.txt"
use_regex: true
register: wildcard_files_to_delete
- name: Ansible remove file wildcard
file:
path: "{{ item.path }}"
state: absent
with_items: "{{ wildcard_files_to_delete.files }}"
For example I want to except a file named "important.txt". How can I do that?
Just add a when condition to the task that deletes files. E.g., something like:
- name: Ansible remove file wildcard
file:
path: "{{ item.path }}"
state: absent
when: item.path != '/etc/wild_card/example/important.txt'
with_items: "{{ wildcard_files_to_delete.files }}"
This will skip a specific file. If you have a list of files to skip you could do this instead:
- name: Ansible remove file wildcard
file:
path: "{{ item.path }}"
state: absent
when: item.path not in files_to_skip
with_items: "{{ wildcard_files_to_delete.files }}"
vars:
files_to_skip:
- /etc/wild_card/example/important.txt
- /etc/wild_card/example/saveme.txt
And if you want to preserve files based on some sort of pattern, you could make use of ansible's match or search tests:
- name: Ansible remove file wildcard
file:
path: "{{ item.path }}"
state: absent
when: item.path is not search('important.txt')
with_items: "{{ wildcard_files_to_delete.files }}"

Playbook where item.stat.exist not working

I have created playbook which will run on a remote host and check whether the files exist or not. I want to extract the only files which are not present on the remote host. But my playbook giving all paths whether they are present or not.
Playbook:-
- name: Playbook for files not present on remote hosts
hosts: source
gather_facts: false
vars:
Filepath: /opt/webapps/obiee/oracle_common/inventory/ContentsXML/comps.xml
tasks:
- name: Getting files location path
shell: grep -i "COMP NAME" {{ Filepath }} |sed 's/^.*INST_LOC="//'|cut -f1 -d'"' | sed '/^$/d;s/[[:blank:]]//g' // extract files from comps.xml
register: get_element_attribute
- name: check path present or not
stat:
path: "{{ item }}"
with_items:
- "{{ get_element_attribute.stdout_lines }}"
register: path_output
- name: path exists or not
set_fact:
path_item: "{{ item }}" # here i am getting the output as expected that's files not present on remote host
with_items: "{{ path_output.results }}"
register: final_output
when: item.stat.exists == False
- debug:
var: final_output # giving both output i.e. files present and absent
- name: Create a fact list
set_fact:
paths: "{{ final_output.results | map(attribute='item.item') | list }}" # i have add this condition " item.stat.exists == False' inside this stmt
- name: Print Fact
debug:
var: paths
The issue resolved by using below command:
- name: Create a fact list
set_fact:
paths: "{{ final_output.results | selectattr('item.stat.exists', 'equalto', false) | map(attribute='item.item') | list }}"
register: config_facts
The following query should get all the file names which don't exsist on the remote host and store them in the fact 'paths':
- name: Create a fact list
set_fact:
paths: "{{ final_output | json_query(query)}}"
vars:
query: "results[?(#._ansible_item_label.stat.exists==`false`)]._ansible_item_label.item"

Resources