In this article, we are going to see practical examples of Ansible copy. We will be seeing various examples of ansible copy
To keep things simple we have created the playbook that you can test in your localhost
itself, In other words, ansible control machine
you can change the hosts variable to a valid remote host group if you would like to try on the remote machines.
Ansible Copy Module
The copy
module executes a simple copy on the file or directory on the local or on the remote machine.
You can use an ansible copy for the following requirements
- To copy files from a local source to a local destination
- To copy files from a remote source to a remote destination (remote_src)
- To copy files from a local source to a remote destination
You can use the fetch
module to copy files from the remote source to local on the other hand.
We will be seeing various examples of Ansible copy in this article. we have one more article on Ansible copy for you to read and explore.
Quick Syntax of Ansible Copy
# copy_file.yml - name: copy files to destination hosts: localhost connection: local tasks: - name: copy src.txt as dest.txt in the same dir copy: src: files/src.txt dest: files/dest.txt tags: - simple_copy
The preceding playbook consists of a single play. The assumptions to execute the above playbook are,
- There exists a
files
directory in the same location as the playbook - There exists a file
src.txt
inside the abovefiles
directory.
The play consists of a task that uses the copy module to copy the “src” to its “dest”.
By default, the ansible copy module does a force copy to the destination and overwrites the existing file when present.
Just copy the above playbook and run it in localhost by following the below commands in order
sseshadr@SSESHADR-M-24FK copy_module % mkdir -p files sseshadr@SSESHADR-M-24FK copy_module % echo "adding a new line to files/src.txt" >> files/src.txt sseshadr@SSESHADR-M-24FK copy_module % sseshadr@SSESHADR-M-24FK copy_module % ansible-playbook copy_file.yml – tags "simple_copy" -v No config file found; using defaults [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [copy files to destination] ***************************************************************************************************************** TASK [Gathering Facts] *************************************************************************************************************************** ok: [localhost] TASK [force copy src.txt as dest.txt in the same dir] ******************************************************************************************** changed: [localhost] => {"changed": true, "checksum": "f292ebaaaa1651f4875e13ea6346b48b9f817421", "dest": "files/dest.txt", "gid": 20, "group": "staff", "md5sum": "85cfe9b0250baed542831184c0a4c4d5", "mode": "0644", "owner": "sseshadr", "size": 65, "src": "/Users/sseshadr/.ansible/tmp/ansible-tmp-1641913099.0320861-74874-262311856787358/source", "state": "file", "uid": 501} PLAY RECAP *************************************************************************************************************************************** localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 sseshadr@SSESHADR-M-24FK copy_module % ls -l files/dest.txt -rw-r--r – 1 sseshadr staff 65 Jan 11 20:28 files/dest.txt
The file src.txt was copied successfully as “files/dest.txt” as we can see from the output of the copy task, changed: true
How to disable Force Copy of Ansible Copy
If the force copy has to be disabled, i.e., ignore the copy task if the file is already present, then use the option force: no
in the copy-module as shown below.
# copy_file.yml
- name: copy files to destination
hosts: localhost
connection: local
tasks:
- name: no force copy src.txt as dest.txt in the same dir
copy:
src: files/src.txt
dest: files/dest.txt
force: no
tags:
- simple_copy_no_force
The output of the preceding playbook is given below
sseshadr@SSESHADR-M-24FK copy_module % ls -l files/dest.txt -rw-r--r – 1 sseshadr staff 65 Jan 11 20:28 files/dest.txt sseshadr@SSESHADR-M-24FK copy_module % sseshadr@SSESHADR-M-24FK copy_module % date Tue Jan 11 20:35:31 IST 2022 sseshadr@SSESHADR-M-24FK copy_module % sseshadr@SSESHADR-M-24FK copy_module % ansible-playbook copy_file.yml – tags "simple_copy_no_force" -v No config file found; using defaults [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [copy files to destination] ***************************************************************************************************************** TASK [Gathering Facts] *************************************************************************************************************************** ok: [localhost] TASK [dont force copy src.txt as dest.txt in the same dir] *************************************************************************************** ok: [localhost] => {"changed": false, "dest": "files/dest.txt", "src": "/Users/sseshadr/ansible-test/module_test/copy_module/files/src.txt"} PLAY RECAP *************************************************************************************************************************************** localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 sseshadr@SSESHADR-M-24FK copy_module % sseshadr@SSESHADR-M-24FK copy_module % ls -l files/dest.txt -rw-r--r – 1 sseshadr staff 65 Jan 11 20:28 files/dest.txt
We can see that the last modified time of the dest.txt is unchanged and the file was not overwritten by src.txt even though it had a new line since we had disabled force copy in our playbook.
Overwrite and backup the original file
What if the copied file contains a few mistakes but the copy module had overwritten the previous version? No worries, we have the option in the ansible copy module to take a backup of the previous version of the destination file. So it's now easy to revert the copy.
# copy_file.yml - name: copy files to destination hosts: localhost connection: local tasks: - name: copy src.txt to files/backup_test and create a backup of src.txt copy: src: files/src.txt dest: files/backup_test/ backup: yes tags: - backup
In this example, we would be copying files/src.txt
to files/backup_test
directory.
The destination filename would also be src.txt
.
To demonstrate this example, the copy was already run once and we would be re-running the same play by adding an extra line to the source file. Only then we would be getting a backup of the original destination file.
sseshadr@SSESHADR-M-24FK copy_module % echo "creating a new line in src.txt" >> files/src.txt sseshadr@SSESHADR-M-24FK copy_module % sseshadr@SSESHADR-M-24FK copy_module % ansible-playbook copy_file.yml – tags "backup" -v No config file found; using defaults [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [copy files to destination] ***************************************************************************************************************** TASK [Gathering Facts] *************************************************************************************************************************** ok: [localhost] TASK [copy src.txt to files/backup_test and create a backup of src.txt] ************************************************************************** changed: [localhost] => {"backup_file": "files/backup_test/src.txt.77306.2022-01-11@20:45:03~", "changed": true, "checksum": "e2a8f9574b6e1b7e39a0c8abdff02b7edc076d28", "dest": "files/backup_test/src.txt", "gid": 20, "group": "staff", "md5sum": "5a4cf003863bec14f92c132143e6d217", "mode": "0644", "owner": "sseshadr", "size": 242, "src": "/Users/sseshadr/.ansible/tmp/ansible-tmp-1641914102.470293-77281-75182788946002/source", "state": "file", "uid": 501} PLAY RECAP *************************************************************************************************************************************** localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 sseshadr@SSESHADR-M-24FK copy_module % ls -l files/backup_test total 16 -rw-r--r – 1 sseshadr staff 242 Jan 11 20:45 src.txt -rw-r--r – 1 sseshadr staff 211 Jan 11 20:44 src.txt.77306.2022-01-11@20:45:03~
As we can see the latest files/src.txt got copied as files/backup_test/src.txt and a backup of the previous version was also created as “src.txt.77306.2022-01-11@20:45:03~”. We can use this file to revert back to the previous version if something nasty happened due to the copy.
Copying file to a non-existing directory
If the destination directory does not exist, the copy
module takes care of creating it and copying the file to the new directory with the same name as the source file name.
# copy_file.yml - name: copy files to destination hosts: localhost connection: local tasks: - name: copy src.txt to a non existing directory copy: src: files/src.txt dest: files/not_dir/ tags: - dir_not_exist
Output of the preceding playbook is given below.
sseshadr@SSESHADR-M-24FK copy_module % ls files/not_dir ls: files/not_dir: No such file or directory sseshadr@SSESHADR-M-24FK copy_module % ansible-playbook copy_file.yml – tags "dir_not_exist" -v No config file found; using defaults [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [copy files to destination] ***************************************************************************************************************** TASK [Gathering Facts] *************************************************************************************************************************** ok: [localhost] TASK [copy src.txt to a non existing directory] ************************************************************************************************** changed: [localhost] => {"changed": true, "checksum": "e2a8f9574b6e1b7e39a0c8abdff02b7edc076d28", "dest": "files/not_dir/src.txt", "gid": 20, "group": "staff", "md5sum": "5a4cf003863bec14f92c132143e6d217", "mode": "0644", "owner": "sseshadr", "size": 242, "src": "/Users/sseshadr/.ansible/tmp/ansible-tmp-1641915480.656468-80209-40624732707130/source", "state": "file", "uid": 501} PLAY RECAP *************************************************************************************************************************************** localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 sseshadr@SSESHADR-M-24FK copy_module % ls files/not_dir src.txt
Copy entire directory
# copy_dir.yml - name: copy module for directories hosts: localhost connection: local tasks: - name: copy dir1 to /tmp copy: src: dir1 dest: /tmp/ directory_mode: tags: - parentdir
The preceding play assumes that there is a directory dir1
in the same location as the playbook. On running the above play, the entire directory dir1
and its contents will be recursively copied to the destination /tmp
as /tmp/dir1
sseshadr@SSESHADR-M-24FK copy_module % mkdir dir1 && mkdir -p dir1/inner_dir1 && touch dir1/outer_file && touch dir1/inner_dir1/inner_file sseshadr@SSESHADR-M-24FK copy_module % ls -Rl dir1 total 0 drwxr-xr-x 3 sseshadr staff 96 Jan 11 21:14 inner_dir1 -rw-r--r – 1 sseshadr staff 0 Jan 11 21:14 outer_file dir1/inner_dir1: total 0 -rw-r--r – 1 sseshadr staff 0 Jan 11 21:14 inner_file sseshadr@SSESHADR-M-24FK copy_module % ansible-playbook copy_dir.yml – tags "parentdir" -v No config file found; using defaults [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [copy module for directories] *************************************************************************************************************** TASK [Gathering Facts] *************************************************************************************************************************** ok: [localhost] TASK [copy dir1 to /tmp] ************************************************************************************************************************* changed: [localhost] => {"changed": true, "dest": "/tmp/", "src": "/Users/sseshadr/ansible-test/module_test/copy_module/dir1"} PLAY RECAP *************************************************************************************************************************************** localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 sseshadr@SSESHADR-M-24FK copy_module % ls -Rl /tmp/dir1 total 0 drwxr-xr-x 3 sseshadr wheel 96 Jan 11 21:14 inner_dir1 -rw-r--r – 1 sseshadr staff 0 Jan 11 21:14 outer_file /tmp/dir1/inner_dir1: total 0 -rw-r--r – 1 sseshadr staff 0 Jan 11 21:14 inner_file
As we can see from the above output, the entire directory was copied recursively to the destination.
Ansible Copy only directory contents
- name: copy module for directories hosts: localhost connection: local tasks: - name: copy contents of dir1 to /tmp/dir1_contents copy: src: dir1/ dest: /tmp/dir1_contents/ directory_mode: tags: - dircontent
If only the contents of the directory need to be copied leaving out the outer directory, then src should end with a forward slash /
which represents the contents of the src directory (in our case, contents of dir1)
sseshadr@SSESHADR-M-24FK copy_module % ansible-playbook copy_dir.yml – tags "dircontent" -v No config file found; using defaults [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [copy module for directories] *************************************************************************************************************** TASK [Gathering Facts] *************************************************************************************************************************** ok: [localhost] TASK [copy contents of dir1 to /tmp/dir1_contents] *********************************************************************************************** changed: [localhost] => {"changed": true, "dest": "/tmp/dir1_contents/", "src": "/Users/sseshadr/ansible-test/module_test/copy_module/dir1/"} PLAY RECAP *************************************************************************************************************************************** localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 sseshadr@SSESHADR-M-24FK copy_module % ls -Rl /tmp/dir1_contents total 0 drwxr-xr-x 3 sseshadr wheel 96 Jan 11 21:20 inner_dir1 -rw-r--r – 1 sseshadr staff 0 Jan 11 21:20 outer_file /tmp/dir1_contents/inner_dir1: total 0 -rw-r--r – 1 sseshadr staff 0 Jan 11 21:20 inner_file
We can see that the outer directory dir1
was left and only its contents were copied to /tmp/dir1_contents
Ansible Copy raw content to file
Not just files, ansible copy module can also write content to a destination file.
This feature of the ansible copy module is very useful if we want to dump some string content or “Ansible variables” into a file (say a dict).
Instead of the src
option, we have to use the content
argument and pass the necessary content to be written as the destination file.
#copy_file.yml - name: copy files to destination hosts: localhost connection: local vars: somedict: key1: value1 key2: value2 tasks: - name: copy content to content_dest.txt copy: content: | Hello from ansible. This is a sample file. This is a sample dict, {{ somedict }} dest: files/content_dest.txt tags: - content
sseshadr@SSESHADR-M-24FK copy_module % ansible-playbook copy_file.yml – tags "content" -v No config file found; using defaults [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [copy files to destination] ***************************************************************************************************************** TASK [Gathering Facts] *************************************************************************************************************************** ok: [localhost] TASK [copy content to content_dest.txt] ********************************************************************************************************** changed: [localhost] => {"changed": true, "checksum": "783f59cb4d9068edf70494178913ff64426d2bea", "dest": "files/content_dest.txt", "gid": 20, "group": "staff", "md5sum": "fb94495fdebe48ba727186eddc14d5af", "mode": "0644", "owner": "sseshadr", "size": 103, "src": "/Users/sseshadr/.ansible/tmp/ansible-tmp-1641916547.4278688-82599-191762476043983/source", "state": "file", "uid": 501} PLAY RECAP *************************************************************************************************************************************** localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 sseshadr@SSESHADR-M-24FK copy_module % cat files/content_dest.txt Hello from ansible. This is a sample file. This is a sample dict, {'key1': 'value1', 'key2': 'value2'}
How to Verify if the copy is successful
So far we have trusted the copy-module to have successfully copied our source file to the destination.
What if we would like to know if the copy was TRULY successful indeed. Normally calculating the checksum of a file is a good technique to verify if two files are identical and there is no loss or corruption of data during the copy.
So we will look into the ways to verify the result of the copy-module by comparing the checksums of the source and destination file.
- name: copy files to destination hosts: localhost connection: local tasks: - block: - name: get properties of src.txt stat: path: files/src.txt checksum_algorithm: sha1 register: src_info - name: copy src.txt to dest.txt copy: src: files/src.txt dest: files/dest.txt force: yes checksum: register: copy_out - name: Fail if copy was a failure fail: msg: "Copy failed!" when: src_info.stat.checksum != copy_out.checksum - name: Print Copy successful debug: msg: "Copy Successful!" tags: - checksum
The stat
module can be used to find the initial checksum of the source file before copying. After copying, the copy-module itself returns a dict that contains the checksum of the destination file (SHA1 checksum).
Since the copy-module returns a “SHA1 checksum”, we can also calculate the “SHA1 checksum” of the source file by passing it as an argument to the stat module.
In the end, both the checksum values are compared to see if they are equal.
sseshadr@SSESHADR-M-24FK copy_module % ansible-playbook copy_file.yml – tags "checksum" -v No config file found; using defaults [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' PLAY [copy files to destination] ***************************************************************************************************************** TASK [Gathering Facts] *************************************************************************************************************************** ok: [localhost] TASK [get properties of src.txt] ***************************************************************************************************************** ok: [localhost] => {"changed": false, "stat": {"atime": 1641914093.9985154, "attr_flags": "", "attributes": [], "birthtime": 1641913076.4996405, "block_size": 4096, "blocks": 8, "charset": "us-ascii", "checksum": "e2a8f9574b6e1b7e39a0c8abdff02b7edc076d28", "ctime": 1641914092.7721033, "dev": 16777220, "device_type": 0, "executable": false, "exists": true, "flags": 0, "generation": 0, "gid": 20, "gr_name": "staff", "inode": 13512847, "isblk": false, "ischr": false, "isdir": false, "isfifo": false, "isgid": false, "islnk": false, "isreg": true, "issock": false, "isuid": false, "mimetype": "text/plain", "mode": "0644", "mtime": 1641914092.7721033, "nlink": 1, "path": "files/src.txt", "pw_name": "sseshadr", "readable": true, "rgrp": true, "roth": true, "rusr": true, "size": 242, "uid": 501, "version": null, "wgrp": false, "woth": false, "writeable": true, "wusr": true, "xgrp": false, "xoth": false, "xusr": false}} TASK [copy src.txt to dest.txt] ****************************************************************************************************************** ok: [localhost] => {"changed": false, "checksum": "e2a8f9574b6e1b7e39a0c8abdff02b7edc076d28", "dest": "files/dest.txt", "gid": 20, "group": "staff", "mode": "0644", "owner": "sseshadr", "path": "files/dest.txt", "size": 242, "state": "file", "uid": 501} TASK [Fail if copy was a failure] **************************************************************************************************************** skipping: [localhost] => {"changed": false, "skip_reason": "Conditional result was False"} TASK [Print Copy successful] ********************************************************************************************************************* ok: [localhost] => { "msg": "Copy Successful!" } PLAY RECAP *************************************************************************************************************************************** localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
The copy was successful as the checksums values are the same before and after copy!
Cheers
Sudarshan Seshadri