With mode=pull, I want to fetch and save remote files to the "dest" directory per hostname under the same top-level directory tree.
This is what I want:
src=/proc/cpuinfo (of every Ansible inventory host)
dest=/tmp/host1/cpuinfo, /tmp/host2/cpuinfo, /tmp/host3/cpuinfo, etc. (of the Ansible master)
If I do,
ansible all -m synchronize 'src=/proc/cpuinfo dest=/tmp/cpuinfo mode=pull'
/tmp/cpuinfo file on the Ansible master (= dest) gets overwritten by every remote host's cpuinfo file and I get to see only the very last one.
That is, I want a similar behavior as if I run
ansible all -m fetch -a 'src=/proc/cpuinfo dest=/tmp/cpuinfo'
Thank you in advance!
Steve
I doubt you can do this with single ad-hoc command.
ansible all -m synchronize -a 'src=/proc/cpuinfo dest=/tmp/{{inventory_hostname}}/cpuinfo mode=pull'
could do the thing, but you must create /tmp/<hostname> directories in advance, because rsync doesn't create non-existent directories for you. And you can't use ansible facts (like ansible_hostname and ansible_fqdn) as parameters for ad-hoc module execution - only "predefined" variables (like inventory_hostname).
Update: playbook code
- file:
path: "/tmp/{{ inventory_hostname }}"
state: directory
delegate_to: localhost
- synchronize:
src: /proc/cpuinfo
dest: "/tmp/{{ inventory_hostname }}/cpuinfo"
mode: pull
(Original poster)
Another way to do it using the synchronize module only:
- synchronize:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: pull
with_items:
- { src: '/proc/cpuinfo', dest: '/tmp/testing/{{ inventory_hostname }}/proc' }
- { src: '/proc/meminfo', dest: '/tmp/testing/{{ inventory_hostname }}/proc' }
- { src: '/etc/services', dest: '/tmp/testing/{{ inventory_hostname }}/etc' }
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"
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
In my files directory I have various files, with a similar name structure:
data-example.zip
data-precise.zip
data-arbitrary.zip
data-collected.zip
I would like to transfer all of these files in the /tmp directory of my remote machine using Ansible without specifying each file name explicitly.
In other words I would like to transfer every file that stars with "data-".
What is the correct way to do that? In a similar thread, someone suggested the with_fileglob keyword, - but I couldn't get that to work.
Can someone provide me an example on how to accomplish said task?
Method 1: Find all files, store them in a variable and copy them to destination.
- hosts: lnx
tasks:
- find: paths="/source/path" recurse=yes patterns="data*"
register: file_to_copy
- copy: src={{ item.path }} dest=/dear/dir
owner: root
mode: 0775
with_items: "{{ files_to_copy.files }}"
Use remote_src: yes to copy file in remote machine from one path to another.
Ansible documentation
Method 2: Fileglob
- name: Copy each file over that matches the given pattern
copy:
src: "{{ item }}"
dest: "/etc/fooapp/"
owner: "root"
mode: 0600
with_fileglob:
- "/playbooks/files/fooapp/*"
Ansible documentation
Shortly after posting the question I actually figured it out myself. The with_fileglob keyword is the way to do it.
- name: "Transferring all data files"
copy:
src: "{{ item }}"
dest: /tmp/
with_fileglob: "data-*"
I am using unarchive module in ansible to extract tar.gz files to a folder. After that, i want to copy the extracted folder to another location. Is there a ansible way of doing it? I tried below , it does not work
- name : Extract Files
unarchive:
src: "/home/app/{{ item }}"
dest: "/home/app/"
mode: 0777
copy: no
with_items:
- "{{ sharef }}"
delegate_to: localhost
register: extractedfiles
- name : Copy Files
copy:
src: "{{extractedfiles.results}}"
dest: "{{ location }}"
delegate_to: localhost
How do i extract the filie names from the previously executed task extractedfiles.results does not work. Any help would be appreciated.
I am trying to copy directories on remote machine using Ansible.
It throws "msg": "Remote copy does not support recursive copy of directory:" error.
below is my ansible playbook.
name: Copy Juddi depenedent directories
copy:
src: "{{ source_vm}}/{{ item }}"
dest: "{{ destination_vm }}/"
remote_src: yes
with_items:
- "dir1"
- "dir2"
- "dir3"
Can anyone please help me?
You can use the synchronize module https://docs.ansible.com/ansible/latest/modules/synchronize_module.html#examples and just replace copy with synchronize.
Read the example link above. For two directories on one remote host use:
- name: Synchronize two directories on one remote host.
synchronize:
src: /first/absolute/path
dest: /second/absolute/path
delegate_to: "{{ inventory_hostname }}"