How ansible can call multiple files under path option as a variable - linux

I am just learning ansible and trying to understand how can i include multiple file in the path option in ansible replace module.
I have three files where i need to replace a old hostname with new hostanme.
Files are :
- /etc/hosts
- /etc/hosts.custom
- /etc/hosts-backup
Below Simple Play works fine:
- name: Replace string in hosts file
hosts: all
gather_facts: false
tasks:
- name: Replace string in host file
replace:
path: /etc/hosts
regexp: "171.20.20.16 fostrain.example.com"
replace: "171.20.20.16 dbfoxtrain.example.com"
backup: yes
However, after lot of googling i see this can be done as follows, but in case i have multiple files and those needs to be called as a variable in different modules, How we can define then in such a way so as to call them by variable name.
Below is Just what i am trying to understand..
- name: Replace string in hosts file
hosts: all
gather_facts: false
tasks:
- name: Checking file contents
slurp:
path: "{{ ?? }}" <-- How to check these three files here
register: fileCheck.out
- debug:
msg: "{{ (fileCheck.out.content | b64decode).split('\n') }}"
- name: Replace string in host file
replace:
path: "{{ item.path }}"
regexp: "{{ item:from }}"
replace: "{{ item:to }}"
backup: yes
loop:
- { path: "/etc/hosts", From: "171.20.20.16 fostrain.example.com", To: "171.20.20.16 dbfoxtrain.example.com"}
- { Path: "/etc/hosts.custom", From: "171.20.20.16 fostrain.example.com", To: "171.20.20.16 dbfoxtrain.example.com"}
- { Path: "/etc/hosts-backup", From: "171.20.20.16 fostrain.example.com", To: "171.20.20.16 dbfoxtrain.example.com"}
I will appreciate any help.

Create couple of variables; a list with all the files, from and to replacement strings or divide them by ip and domain. Then loop over all the files using the file list variable and use from and to replacement variables for each file. If multiple ip and domain mapping is required then you need to adjust the structure further. So recommend going through ansible documentation on using variables and loops for more details.
Playbook may look like below. Have used a minor regex and you can adjust as required.
- name: Replace string in hosts file
hosts: all
gather_facts: false
vars:
files:
- /etc/hosts
- /etc/hosts.custom
- /etc/hosts-backup
from_ip: "171.20.20.16"
from_dn: "fostrain.example.com"
to_ip: "171.20.20.16"
to_dn: "dbfoxtrain.example.com"
tasks:
- name: Replace string in host file
replace:
path: "{{ item }}"
regexp: "{{ from_ip }}\\s+{{ from_dn }}"
replace: "{{ to_ip }} {{ to_dn }}"
loop: "{{ files }}"
If you want to see the contents of each file then slurp and debug modules can be used like below:
- slurp:
path: "{{ item }}"
loop: "{{ files }}"
register: contents
- debug:
msg: "{{ (item.content | b64decode).split('\n') }}"
loop: "{{ contents.results }}"

Related

Fetch files and remove them from source if succesful

I've been using Ansible to fetch files from Windows nodes to a Linux node for some time with good results.
I would now like the nodes to remove fetched files once they have uploaded successfully.
However, since I'm fetching from lots of endpoints in various states, some files occasionally fail to transfer - and I'm having trouble using Ansible to skip those files, and those files only.
Here's what I have so far:
- name: Find .something files
ansible.windows.win_find:
paths: 'C:\Some\Path'
patterns: [ '*.something' ]
recurse: yes
age_stamp: ctime
age: -1w
register: found_files
- name: Create destination directory
file:
path: "/some/path/{{inventory_hostname}}/"
state: directory
delegate_to: localhost
- name: Fetch .something files
fetch:
src: "{{ item.path }}"
dest: "/some/path/{{inventory_hostname}}/"
flat: yes
validate_checksum: no
with_items: "{{ found_files.files }}"
register: item.sync_result
ignore_errors: yes
msg: "Would remove {{ item.path }}"
when: sync_result is succeeded
with_items: "{{ found_files.files }}"
The problem is, the sync_result variable seems to apply to each node instead of each file - that is, if one file has failed to transfer, no files will be deleted.
I've tried various loops and lists and could not get it to work.
Any pointers would be greatly appreciated.
In a nutshell:
- name: Find .something files
ansible.windows.win_find:
paths: 'C:\Some\Path'
patterns: [ '*.something' ]
recurse: yes
age_stamp: ctime
age: -1w
register: find_something
- name: Create destination directory
file:
path: "/some/path/{{ inventory_hostname }}/"
state: directory
delegate_to: localhost
- name: Fetch .something files
fetch:
src: "{{ item.path }}"
dest: "/some/path/{{ inventory_hostname }}/"
flat: yes
validate_checksum: no
loop: "{{ find_something.files }}"
register: fetch_sync
ignore_errors: yes
- name: Delete successfully fetched files
file:
path: "{{ item.file }}"
state: absent
loop: "{{ fetch_sync.results | select('succeeded') }}"
# If you are using ansible < 2.10 you need to cast to list i.e.
# loop: "{{ fetch_sync.results | select('succeeded') | list }}"

Ansible - load multiplne local yml files and merge their content

my host_vars file has about 5k lines of yml code. So I would like to have separate yml files - one file per one service.
Simplified example:
user#test $ cat production/split_configs/a.example.net.yml
my_array:
- a.example.net
user#test $ cat production/split_configs/b.example.net.yml
my_array:
- b.example.net
user#test $ cat webhosts.yml
- hosts: myservers
pre_tasks:
- name: merge ansible arrays
tags: always
delegate_to: localhost
block:
- name: find config files
find:
paths: production/configs/
patterns: '*.yml'
register: find_results
- name: aaa
debug:
msg: "{{ find_results.files }}"
- name: bbb
debug:
msg: "{{ item.path }}"
with_items: "{{ find_results.files }}"
- name: ccc
debug:
msg: "{{ lookup('file', 'production/configs/a.example.net.yml') }}"
- name: ddd
debug:
msg: "{{ lookup('file', item.path) }}"
loop: "{{ find_results.files }}"
tasks:
- name: eee
debug:
msg: "{{ my_array }}"
The goal is to merge content of both arrays an print the merged content in task eee:
my_array:
- a.example.net
- b.example.net
Task aaa print informations about files (path, mode, uid, ...) - it works.
Tasks bbb, and ddd print nothing. I do not understand why.
Task ccc print content of file. But the path is written in playbook :-(
After load files I need to merge them. My idea is to use something like set_fact: my_array="{{ my_array + my_array }}" in task with with_items: "{{ find_results.files }}". Is it good idea? Or how better I can do it?
For example, the tasks below do the job
- include_vars:
file: "{{ item }}"
name: "my_prefix_{{ item|regex_replace('\\W', '_') }}"
with_fileglob: production/split_configs/*
- set_fact:
my_vars: "{{ my_vars|d({})|
combine(lookup('vars', item),
recursive=True,
list_merge='append') }}"
loop: "{{ q('varnames', 'my_prefix_.*') }}"
give
my_vars:
my_array:
- a.example.net
- b.example.net
You can use the simple cat commnd to merge the files into the one file and later include var file for example -
- raw: "cat production/split_configs/* >my_vars.yml"
- include_vars: file=my_vars.yml name=my_vars
will give you the result -
my_vars:
my_array:
- a.example.net
- b.example.net

How to rename a file in ansible

I need to rename hosts_example or hosts_Example to be named as hosts_real if any of the file exists
- name: Playbook to Standardize Hosts
hosts: test
vars:
destpath: /etc/hosts_real
filename: [ /etc/hosts_example,/etc/hosts_Example ]
tasks:
- name: Check if file exists
stat:
path: "{{ item }}"
with_items:
- "{{ filename }}"
register: check_file_name
- debug:
msg: "{{check_file_name}}"
- name: Rename file
command: mv "{{ item }}"{{destpath}}"
with_items:
- "{{ check_file_name.results }}"
when: item.stat.exists == true
I tried this am getting errors and not able to achieve the desired result
name: replace file
shell: mv /local/oracle/12.2/oldora.ora /local/oracle/12.2/tnsnames.ora
become: appuser
Works for us. Update your path in the shell command. I dont think the file module has a rename command. If you have a variable in your shell line put the whole line in quotes like:
shell: "mv {{ path_1 }} {{ path_2 }}"

Ansible aclling two diffrent file variables into single task looping through multiple files

Content of fil1
# cat file1
fostrain01.example.com
fostrain02.example.com
fostrain03.example.com
Content of file2
# cat fil2
ServerIPS 171.20.20.16 171.20.20.17 171.20.20.18 171.20.20.19 171.20.20.20
ServerIPS 171.20.20.21 171.20.20.22 171.20.20.23 171.20.20.24 171.20.20.25
In the below playbook, its replacing the contents on two lines one is hostname as str and in another line replacing the ip So, i have taken the two different task 1) Replace strings in file & 2) Replace ip in file to accomplish this calling the variable defined.
Playbook:
- name: Replace string in hosts file
hosts: all
gather_facts: false
vars:
files:
- /etc/file1
- /etc/file2
from_str: "fostrain01.example.com"
to_str: "dbfoxtrain01.example.com"
from_ip: "^(.*)171\\.20\\.20\\.18(.*)$"
to_ip: "\\g<1>172.20.20.18\\g<2>"
tasks:
- name: Replace strings in file
replace:
path: "{{ item}}"
regexp: "{{ from_str }}"
replace: "{{ to_str }}"
backup: yes
loop: "{{ files }}"
- name: Replace ip in file
replace:
path: "{{ item}}"
regexp: "{{ from_ip }}"
replace: "{{ to_ip }}"
backup: yes
loop: "{{ files }}"
Can we write the task as follows something where we don't really write to two different tasks, i tried but not getting how to loops through "{{ files }} with below approach.
tasks:
- name: Replace elements in file
replace:
path: "{{ item.path }}"
regexp: "{{ item.From }}"
replace: "{{ item.To }}"
backup: yes
loop:
# Replace the desired string
- { path: "{{ item }}", From: "{{ from_str }}", To: "{{ to_str }}" }
# Replace the desired ip
- { path: "{{ item}}", From: "{{ from_ip }}", To: "{{ to_ip}}" }
What is Desired Change:
task 1) Replace strings in file & task 2) Replace ip in file to be clubbed into One.
Expected Results:
# cat file1
dbfoxtrain01.example.com <-- Changed
fostrain02.example.com
fostrain03.example.com
# cat fil2
ServerIPS 171.20.20.16 171.20.20.17 172.20.20.18 171.20.20.19 171.20.20.20 <-- changed here ^172
ServerIPS 171.20.20.21 171.20.20.22 171.20.20.23 171.20.20.24 171.20.20.25
The task below does the job
- replace:
path: "{{ item.path }}"
regexp: "{{ item.from }}"
replace: "{{ item.to }}"
loop:
- path: file1
from: 'fostrain01\.example\.com'
to: 'dbfoxtrain01.example.com'
- path: file2
from: '171\.20\.20\.18'
to: '172.20.20.18'
Example
shell> tree .
.
├── file1
├── file1.orig
├── file2
├── file2.orig
└── test.yml
0 directories, 5 files
shell> cat file1
fostrain01.example.com
fostrain02.example.com
fostrain03.example.com
shell> cat file2
ServerIPS 171.20.20.16 171.20.20.17 171.20.20.18 171.20.20.19 171.20.20.20
ServerIPS 171.20.20.21 171.20.20.22 171.20.20.23 171.20.20.24 171.20.20.25
shell> cat test.yml
- hosts: localhost
gather_facts: false
tasks:
- replace:
path: "{{ item.path }}"
regexp: "{{ item.from }}"
replace: "{{ item.to }}"
loop:
- path: file1
from: 'fostrain01\.example\.com'
to: 'dbfoxtrain01.example.com'
- path: file2
from: '171\.20\.20\.18'
to: '172.20.20.18'
shell> ansible-playbook test.yml
PLAY [localhost] ************************************************
TASK [replace] **************************************************
changed: [localhost] => (item={'path': 'file1', 'from': 'fostrain01\\.example\\.com', 'to': 'dbfoxtrain01.example.com'})
changed: [localhost] => (item={'path': 'file2', 'from': '171\\.20\\.20\\.18', 'to': '172.20.20.18'})
PLAY RECAP ******************************************************
localhost: ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
shell> cat file1
dbfoxtrain01.example.com
fostrain02.example.com
fostrain03.example.com
shell> cat file2
ServerIPS 171.20.20.16 171.20.20.17 172.20.20.18 171.20.20.19 171.20.20.20
ServerIPS 171.20.20.21 171.20.20.22 171.20.20.23 171.20.20.24 171.20.20.25

Ansible loops for assigning sequential integers as hostnames

I'm new to Ansible. I have the following playbook that changes the hostname for a remote server:
---
- hosts: dbservers
remote_user: testuser1
become: yes
become_method: sudo
vars:
LOCAL_HOSTNAME: 'db01'
LOCAL_DOMAIN_NAME: 'ansibletest.com'
tasks:
# Checks and removed the existing occurences of <IP hostname FQDN> from /etc/hosts
- name: Remove occurences of the existing IP
lineinfile: dest=/etc/hosts
regexp='{{ hostvars[item].ansible_default_ipv4.address }}'
state=absent
when: hostvars[item].ansible_default_ipv4.address is defined
with_items: "{{ groups['dbservers'] }}"
# Adds the IP in the format <IP hostname FQDN> to /etc/hosts
- name: Add the IP and hostname to the hosts file
lineinfile: dest=/etc/hosts
regexp='.*{{ item }}$'
line="{{ hostvars[item].ansible_default_ipv4.address }} {{ LOCAL_HOSTNAME }} {{ LOCAL_HOSTNAME }}.{{ LOCAL_DOMAIN_NAME }}"
state=present
when: hostvars[item].ansible_default_ipv4.address is defined
with_items: "{{ groups['dbservers'] }}"
- name: Remove HOSTNAME occurences from /etc/sysconfig/network
lineinfile: dest=/etc/sysconfig/network
regexp='^HOSTNAME'
state=absent
when: hostvars[item].ansible_default_ipv4.address is defined
with_items: "{{ groups['dbservers'] }}"
- name: Add new HOSTNAME to /etc/sysconfig/network
lineinfile: dest=/etc/sysconfig/network
regexp='^HOSTNAME='
line="HOSTNAME={{ LOCAL_HOSTNAME }}.{{ LOCAL_DOMAIN_NAME }}"
state=present
when: hostvars[item].ansible_default_ipv4.address is defined
with_items: "{{ groups['dbservers'] }}"
- name: Set up the hostname
hostname: name={{ LOCAL_HOSTNAME }}.{{ LOCAL_DOMAIN_NAME }}
In this example, LOCAL_HOSTNAME is already assigned a value of db01. And in this scenario, the dbservers group has only one server:
[dbservers]
192.168.1.93
However, I also have 2 other servers that are designated to be webservers:
[webservers]
192.168.1.95
192.168.1.96
[dbservers]
192.168.1.93
The aim is to name them as web01.domain, web02.domain and so on.
As per the docs it seems that this could be achieved by using with_sequence.
My question is, is it possible (in Ansible) to use 2 variables in loops? Something along the lines of the pseudo-code below:
i=1
for host in webservers:
open host(/etc/hosts):
add "IP<space>HOSTNAME{i}<space>"<space>"HOSTNAME{i}.FQDN"
i++
Could this be achieved using playbooks or am I approaching the issue in an wrong way?
Generate indexed hostname first, define it as hostfact and use it later to fill other servers' hosts files.
- hosts: webservers
gather_facts: no
tasks:
- set_fact:
indexed_hostname: "{{ 'web{0:02d}'.format(play_hosts.index(inventory_hostname)+1) }}"
- hosts: dbservers
gather_facts: no
tasks:
- debug:
msg: "{{ hostvars[item].indexed_hostname }}"
with_items: "{{ groups['webservers'] }}"
Also there is such thing as with_indexed_items.

Resources