I am trying to build an rsync type backup on multiple servers. I would like to create a backup directory per server locally on my laptop and then back them up. If the directory does not exist create it.
I start off by calling the playbook locally, so that I can create the directories locally, then change the playbook to the backup group. The issue is that I dont know how to populate the hostnames in the backup group. When I run the playbook below the only directory that gets created is localhost. I need for each host in the backup group to create a local directory and back it up. what would be the easiest way to make this work?
- hosts: localhost
become: yes
#strategy: free
pre_tasks:
vars:
- backupDir: "/Users/user1/Desktop/Fusion/backups/{{ inventory_hostname }}/"
roles:
tasks:
- name: Check if Backup Folder Exisits.
stat:
path: "{{ backupDir }}"
register: my_folder
- name: "Ansible Create directory if not exists"
file:
path: "{{ backupDir }}"
state: directory
when: my_folder.stat.exists == false
- hosts: backup
tasks:
- name: Rsync Directories from Remote to Local
synchronize:
mode: pull
src: "{{ item }}"
dest: "{{ backupDir }}/{{ansible_date_time.date}}.back"
with_items:
- "/home/user1/"
- "/var/www/html/"
- "/root/"
when: my_folder.stat.exists
handlers:
In that case, I think you're looking for the loop module.
something like this..
- name: "Ansible Create directory if not exists"
file:
path: "{{ backupDir }}"
state: directory
when: my_folder.stat.exists == false
loop: {{ inventory_hostname }}
https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html
In your inventory file you can create groups that tie back to your hosts you're calling on.
[localhost]
127.0.0.1
[backup]
host1
host2
host3
Related
I have 2 remote servers (Prod and Demo) and I would like to copy the latest file from a particular folder in Prod to another folder in Demo. Only one file is to be copied.
I can find the latest file in Prod using:
- name: Get files in folder
find:
paths: "/path_in_prod/arch/"
register: found_files
become: true
become_user: root
delegate_to: "{{ prod_server }}"
when: copy_content_from_prod is defined
- name: Get latest file
set_fact:
latest_file: "{{ found_files.files | sort(attribute='mtime', reverse=true) | first }}"
become: true
become_user: root
delegate_to: "{{ prod_server }}"
when: copy_content_from_prod is defined
I can check I have the correct file (debug).
When I try to copy the file with
- name: Fetch the file from prod
fetch: src= {{ latest_file.path }} dest=buffer/ flat=yes
delegate_to: "{{ prod_server }}"
- name: Copy the file to demo
copy: src=buffer/{{ latest_file.path | basename }} dest=/path_in_demo/in
I get a "File not found" error. But if I look for the file it is there (latest_file.path on Prod).
this is the error message
fatal: [demoServerHost -> ProdServerHost ]: FAILED! => {"changed": false, "msg": "file not found: "}
I do not know if I am interpreting the error message correctly but it seems to be looking in Demo in order to copy onto Prod?
In such case the synchronize_module might be the solution.
- name: Synchronize file from PROD to DEMO
synchronize:
src: "/tmp/test.txt"
dest: "/tmp/test.txt"
mode: push
delegate_to: "{{ prod_server }}"
when: "{{ demo_server }}"
which is "copying" a file from the production node to the demo node.
There are also a lot of answers under How to copy files between two nodes using Ansible.
I have faced a similar issue, where the copy task hangs indefinitely. Here is my example which is not site specific (will identify the site and user using the options).
The easiest solution I have found is to scp directly using the shell module:
- name: scp files onto '{{ target_destination }}' looping for each file on '{{ target_source }}'
shell: 'scp {{ hostvars[target_source].ansible_user }}#{{ hostvars[target_source].ansible_host }}:/opt/{{ hostvars[target_source].ansible_user }}/{{ item }} /opt/{{ destuser.stdout }}'
loop: '{{ diffout.stdout_lines }}'
when: diffout.stdout != ""
Some notes:
"target_source" and "target_destination" are defined using the extra-vars option
diffout is an earlier task comparing the folders on "Prod" and "Demo" and shows any new files to copy
this task is run on the "target_destination" (in my case Prod)
hostvars[target_source] will look at the variables for the "target_source" host in the inventory
this serves as a "pull" from Demo to Prod in my case, if your "Demo" doesn't have permissions, then you could delegate the task to "Prod" and rearrange the scp to look for "Demo" vars to push from "Prod"
I had our security group ask for all the information we gather from the hosts we manage with Ansible Tower. I want to run the setup command and put it into a file in a folder I can run ansible-cmdb against. I need to do this in a playbook because we have disabled root login on the hosts and only allow public / private key authentication of the Tower user. The private key is stored in the database so I cannot run the setup command from the cli and impersonate the Tower user.
EDIT I am adding my code so it can be tested elsewhere.
gather_facts.yml
---
- hosts: all
gather_facts: true
become: true
tasks:
- name: Check for temporary dir make it if it does not exist
file:
path: /tmp/ansible
state: directory
mode: 0755
- name: Gather Facts into a file
copy:
content: '{"ansible_facts": {{ ansible_facts | to_json }}}'
dest: /tmp/ansible/{{ inventory_hostname }}
cmdb_gather.yml
---
- hosts: all
gather_facts: no
become: true
tasks:
- name: Fetch fact gather files
fetch:
src: /tmp/ansible/{{ inventory_hostname }}
dest: /depot/out/
flat: yes
CLI:
ansible -i devinventory -m setup --tree out/ all
This would basically look like (wrote on spot not tested):
- name: make the equivalent of "ansible somehosts -m setup --tree /some/dir"
hosts: my_hosts
vars:
treebase: /path/to/tree/base
tasks:
- name: Make sure we have a folder for tree base
file:
path: "{{ treebase }}"
state: directory
delegate_to: localhost
run_once: true
- name: Dump facts host by host in our treebase
copy:
dest: "{{ treebase }}/{{ inventory_hostname }}"
content: '{"ansible_facts": {{ ansible_facts | to_json }}}'
delegate_to: localhost
Ok I figured it out. While #Zeitounator had a correct answer I had the wrong idea. In checking the documentation for ansible-cmdb I caught that the application can use the fact cache. When I included a custom ansible.cfg in the project folder and added:
[defaults]
fact_caching=jsonfile
fact_caching_connection = /depot/out
ansible-cmdb was able to parse the output correctly using:
ansible-cmdb -f /depot/out > ansible_hosts.html
Using Ansible 2.9.12
Question: How do I configure Ansible to ensure the contents of a file is equal amongst at least 3 hosts, when the file is present at at least one host?
Imagine there are 3 hosts.
Host 1 does not has /file.txt.
Host 2 has /file.txt with contents hello.
Host 3 has /file.txt with contents hello.
Before the play is run, I am unaware whether the file is present or not. So the file could exist on host1, or host2 or host3. But the file exists on at least one of the hosts.
How would I ensure each time Ansible runs, the files across the hosts are equal. So in the end, Host 1 has the same file with the same contents as Host 2 or Host 3.
I'd like this to be dynamically set, instead of specifying the host names or group names, e.g. when: inventory_hostname == host1.
I am not expecting a check to see whether the contents of host 2 and 3 are equal
I do however, want this to be setup in an idempotent fashion.
The play below does the job, I think
shell> cat pb.yml
- hosts: all
tasks:
- name: Get status.
stat:
path: /file.txt
register: status
- block:
- name: Create dictionary status.
set_fact:
status: "{{ dict(keys|zip(values)) }}"
vars:
keys: "{{ ansible_play_hosts }}"
values: "{{ ansible_play_hosts|
map('extract', hostvars, ['status','stat','exists'])|
list }}"
- name: Fail. No file exists.
fail:
msg: No file exists
when: status.values()|list is not any
- name: Set reference to first host with file present.
set_fact:
reference: "{{ status|dict2items|
selectattr('value')|
map(attribute='key')|
first }}"
- name: Fetch file.
fetch:
src: /file.txt
dest: /tmp
delegate_to: "{{ reference }}"
run_once: true
- name: Copy file if not exist
copy:
src: "/tmp/{{ reference }}/file.txt"
dest: /file.txt
when: not status[inventory_hostname]
But, this doesn't check the existing files are in sync. It would be safer to sync all hosts, I think
- name: Synchronize file
synchronize:
src: "/tmp/{{ reference }}/file.txt"
dest: /file.txt
when: not status[inventory_hostname]
Q: "FATAL. could not find or access '/tmp/test-multi-01/file.txt on the Ansible controller. However, folder /tmp/test-multi-03 is present with the file.txt in it."
A: There is a problem with the fetch module when the task is delegated to another host. When the TASK [Fetch file.] is delegated to test-multi-01 which is localhost in this case changed: [test-multi-03 -> 127.0.0.1] the file will be fetched from test-multi-01 but will be stored in /tmp/test-multi-03/file.txt. The conclusion is, the fetch module ignores delegate_to when it comes to creating host-specific directories (not reported yet).
As a workaround, it's possible to set flat: true and store the files in a specific directory. For example, add the variable sync_files_dir with the directory, set fetch flat: true, and use the directory to both fetch and copy the file
- hosts: all
vars:
sync_files_dir: /tmp/sync_files
tasks:
- name: Get status.
stat:
path: /file.txt
register: status
- block:
- name: Create dir for files to be fetched and synced
file:
state: directory
path: "{{ sync_files_dir }}"
delegate_to: localhost
- name: Create dictionary status.
set_fact:
status: "{{ dict(keys|zip(values)) }}"
vars:
keys: "{{ ansible_play_hosts }}"
values: "{{ ansible_play_hosts|
map('extract', hostvars, ['status','stat','exists'])|
list }}"
- debug:
var: status
- name: Fail. No file exists.
fail:
msg: No file exists
when: status.values()|list is not any
- name: Set reference to first host with file present.
set_fact:
reference: "{{ status|dict2items|
selectattr('value')|
map(attribute='key')|
first }}"
- name: Fetch file.
fetch:
src: /file.txt
dest: "{{ sync_files_dir }}/"
flat: true
delegate_to: "{{ reference }}"
run_once: true
- name: Copy file if not exist
copy:
src: "{{ sync_files_dir }}/file.txt"
dest: /file.txt
when: not status[inventory_hostname]
We can achieve it by fetching the file from hosts where the file exists. The file(s) will be available on the control machine. However if the file which will be the source, exists on more than 1 node, then there will be no single source of truth.
Consider an inventory:
[my_hosts]
host1
host2
host3
Then the below play can fetch the file, then use that file to copy to all nodes.
# Fetch the file from remote host if it exists
- hosts: my_hosts
tasks:
- stat:
path: /file.txt
register: my_file
- fetch:
src: /file.txt
dest: /tmp/
when: my_file.stat.exists
- find:
paths:
- /tmp
patterns: file.txt
recurse: yes
register: local_file
delegate_to: localhost
- copy:
src: "{{ local_file.files[0].path }}"
dest: /tmp
If multiple hosts had this file then it would be in /tmp/{{ ansible_host }}. Then as we won't have a single source of truth, our best estimate can be to use the first file and apply on all hosts.
Well i believe the get_url module is pretty versatile - allows for local file paths or paths from a web server. Try it and let me know.
- name: Download files in all host
hosts: all
tasks:
- name: Download file from a file path
get_url:
url: file:///tmp/file.txt
dest: /tmp/
Edited ans:
(From documentation: For the synchronize module, the “local host” is the host the synchronize task originates on, and the “destination host” is the host synchronize is connecting to)
- name: Check that the file exists
stat:
path: /etc/file.txt
register: stat_result
- name: copy the file to other hosts by delegating the task to the source host
synchronize:
src: path/host
dest: path/host
delegate_to: my_source_host
when: stat_result.stat.exists
I'm trying to write a playbook that will rsync the folders from source to target after a database refresh. Our Peoplesoft HR application also requires a filesystem refresh along with database. I'm new to ansible and not an expert with python. I've written this but my playbook fails if any of the with_items doesn't exist. I'd like to use this playbook for all apps and the folders may differ between apps. How can I skip the folders that doesn't exist in source. I'm passing {{ target }} at command line.
---
- hosts: '<hostname>'
remote_user: <user>
tasks:
- shell: ls -l /opt/custhome/prod/
register: folders
- name: "Copy PROD filesystem to target"
synchronize:
src: "/opt/custhome/prod/{{ item }}"
dest: "/opt/custhome/dev/"
delete: yes
when: "{{ folders == item }}"
with_items:
- 'src/cbl/'
- 'sqr/'
- 'bin/'
- 'NVISION/'
In this case, NVISION doesn't exist in HR app but it does in FIN app. But the playbook is failing coz that folder doesn't exist in source.
You can use find module to find and store paths to source folders and then to iterate over results. Example playbook:
- hosts: '<hostname>'
remote_user: <user>
tasks:
- name: find all directories
find:
file_type: directory
paths: /opt/custhome/prod/
patterns:
- "src"
- "sqr"
- "bin"
register: folders
#debug to understand contents of {{ folders }} variable
# - debug: msg="{{ folders }}"
- name: "Copy PROD filesystem to target"
synchronize:
src: "{{ item.path }}"
dest: "/opt/custhome/dev/"
delete: yes
with_items: "{{ folders.files }}"
You may want to use recurse to descend into subdirectories and use_regex to use the power of python regex instead of shell globbing.
Below is a part of a playbook in Ansible 2.1:
- hosts: localhost
any_errors_fatal: true
tasks:
- name: Bla Bla
file: path=/var/tmp/somedir state=directory
#ignore_errors: no
- name: Create directory for every host
file: path=/var/tmp/somedir/{{ item }} state=directory
with_items: "{{ groups['XYZ'] }}"
- name: Get File contents of NewFile
shell: cat NewFile.txt executable=/bin/bash
register: file_contents
- hosts: XYZ
#any_errors_fatal: true
vars:
num_hosts: "{{ groups['XYZ'] | length }}"
serial: num_hosts
tasks:
- name: Copy files to corresponding directories
vars:
path: /var/tmp/somedir/{{ item[0] }}
synchronize: mode=pull src={{ item[1] }} dest={{ path }}
with_nested:
- "{{ groups['XYZ'] }}"
- with_lines: cat NewFile.txt
This does not work.
Now the problem is i am not able to reference file_contents which has been registered under localhost and Ansible is not supporting to cat the NewFile from the hosts: XYZ
Is there any way to do this in some simple manner? I need to check contents of the NewFile in this playbook only and then use the same to copy files from remote to local.
As mentioned in the comments, facts (or all variables) are stored on a host basis. If you have registered a values from a task running on localhost, you can access it from any task running in context of other hosts through the global hostvars dict. All hosts and their facts are stored in there:
hostvars['localhost']['file_contents']
I am not entirely sure simply registered variables are available in the hostvars dict. If not, you have to use set_fact in the first play to store it as a fact.