258

How is it possible to move/rename a file/directory using an Ansible module on a remote system? I don't want to use the command/shell tasks and I don't want to copy the file from the local system to the remote system.

Timur Shtatland
  • 12,024
  • 2
  • 30
  • 47
Christian Berendt
  • 3,416
  • 2
  • 13
  • 22
  • 1
    Why don't you want to use command/shell? – Nick Urban Jun 16 '14 at 19:51
  • 4
    Just wanted to know if there is a way without using the mentioned tasks. Looks like there is no other way at the moment. – Christian Berendt Jun 22 '14 at 18:21
  • 1
    Why do you want to move it specifically instead of copy it? That seems like a one-off action, rather than an idempotent ensuring-the-state-of-the-system type of step. – Nick Urban Jun 23 '14 at 19:07
  • 2
    I have a sample configuration file included in a RPM package and I want to move this sample configuration file. – Christian Berendt Jun 23 '14 at 19:36
  • How about creating a symlink to the file? Or doing a get_url on the source file (it's probably available online somewhere if it's open-source). – Nick Urban Jun 24 '14 at 14:34
  • 1
    At the moment I'm using a symlink to reference the file. Using get_url is no option for me because the system cannot reach the internet. – Christian Berendt Jun 25 '14 at 08:25
  • Ansible 2.0 have added an option which takes care of this issue on the copy module: "remote_src". – Arthur Alvim Jan 02 '16 at 13:50
  • I suggested add a module function in order to avoid non-portable commands at https://github.com/ansible/ansible/issues/51694. – Kalle Richter Feb 04 '19 at 11:53
  • What is more generic than moving a file? Answer: nothing. What would posses somebody to then use Ansible to invoke a proprietary shell command for something that is so clearly generic? Not "getting it" I suppose. Please see more common sense solution below. – Rick O'Shea Sep 11 '19 at 14:57

14 Answers14

263

From version 2.0, in copy module you can use remote_src parameter.

If True it will go to the remote/target machine for the src.

- name: Copy files from foo to bar
  copy: remote_src=True src=/path/to/foo dest=/path/to/bar

If you want to move file you need to delete old file with file module

- name: Remove old files foo
  file: path=/path/to/foo state=absent

From version 2.8 copy module remote_src supports recursive copying.

Alex
  • 5,728
  • 5
  • 20
  • 20
  • 30
    Small remark: "Currently remote_src does not support recursive copying." taken from the ansible module doc. So if you want to copy recursively you still need the shell/command module. – klaas Jun 22 '16 at 15:06
  • 1
    If you want to copy many files you have to use synchronize module. From [ansible](http://docs.ansible.com/ansible/copy_module.html) : _"The “copy” module recursively copy facility does not scale to lots (>hundreds) of files. For alternative, see [synchronize module](http://docs.ansible.com/ansible/synchronize_module.html), which is a wrapper around rsync."_ – Alex Jun 23 '16 at 15:18
  • 36
    I don't understand. Copying then deleting is not the same as moving. For one, it's not atomic. For another, it's slower, especially for large files. I'm new to Ansible, but this seems really weird to me. – mlissner Sep 16 '16 at 23:06
  • @mlissner Logically it's the same but in file systems it's not the same as copying and deleting. For this time this is only one right way for moving file in the remote host (without `command` module). – Alex Sep 17 '16 at 18:44
  • 26
    @alex what I'm saying is can't be the right way to do this. I'm going upwind against 50 something upvotes, but this is crazy. Another issue: permissions and other attributes aren't maintained. Another: What if the file [is changed during the copy](http://unix.stackexchange.com/a/6928)? – mlissner Sep 17 '16 at 18:57
  • 1
    @alex also if what you want to do is effectively rename a directory containing hundreds of MB of files, copy/sync then delete is much much worse. (And if big enough you could run out of space during the copy.) I'll stick to `command: mv foo bar` for my use case. – Hamish Downer Sep 20 '17 at 12:31
  • 2
    @Hamish Downer and mlissner. I didn't say that it's the best solution for all your needs. Also I wrote that if you want to copy many files you shouldn't use copy module. Read the question "I don't want to use the command/shell tasks". – Alex Sep 21 '17 at 14:23
  • 1
    @AlecWenzowski Yes, sure! I think everyone know this. What's the main idea of your comment? Downvoting answer?) – Alex Nov 25 '17 at 07:44
  • 11
    @Alex this is the second highest voted answer on a question about idempotently moving files. The question is not about copying. There are many problems with copying instead of moving therefore this answer is incorrect. So it gets a downvote. Etiquette on SO is to explain downvotes. As noted elsewhere, the best option so far is to `command: mv /path/to/foo /path/to/bar creates=/path/to/bar removes=/path/to/foo` – Alec Wenzowski Nov 29 '17 at 22:29
  • Beware ansible copy module is very stupid it doesn't do privilege escalation for src file/directory therefore if there is file/directory in src which need privilege escalation before you read then the task will fail! – garlicFrancium Apr 12 '19 at 19:28
  • 2
    This answer is just WRONG for the question, and a it's a horrible practice to encourage. I'm saddened for the community that @Alex does not accept the pushback here and just edit/delete his answer. If bad upvotes really matter that speaks more to pride mattering more than a good answer. – Scott Prive May 03 '19 at 13:16
  • @Crossfit_and_Beer that question was discussed in comments already. Copying isn't moving I agree with that many times before! That's one more way how you can do some tasks using copy/file module (because you can set some additional parameters and control more return values that command module doesn't provide). – Alex May 06 '19 at 13:48
  • 1
    All good @Alex. For anyone considering alternative approaches: what I did was "file" with "state=absent" to remove the file. Then I redeploy it using "copy:". In my case, I just want to disable a cron job, and I wanted to do this using "pure Ansible" because avoiding command/shell makes dry-runs (--check) and the output simpler for other people to read. – Scott Prive May 08 '19 at 16:40
  • 1
    `remote_src' supports recursive copying as of version 2.8. – Vsevolod Jul 25 '19 at 14:54
  • 1
    this is not good. If you have a 100GB file, this is a mess. – Wang Jul 14 '20 at 13:20
  • 1
    As others mentioned, this "solution" is not idempotent at all, which is the foundation of anything Ansible. – bviktor Jan 14 '22 at 21:10
258

The file module doesn't copy files on the remote system. The src parameter is only used by the file module when creating a symlink to a file.

If you want to move/rename a file entirely on a remote system then your best bet is to use the command module to just invoke the appropriate command:

- name: Move foo to bar
  command: mv /path/to/foo /path/to/bar

If you want to get fancy then you could first use the stat module to check that foo actually exists:

- name: stat foo
  stat: path=/path/to/foo
  register: foo_stat

- name: Move foo to bar
  command: mv /path/to/foo /path/to/bar
  when: foo_stat.stat.exists
Bruce P
  • 19,995
  • 8
  • 63
  • 73
  • 1
    Thanks for your answer. But like mentioned in the question I don't want to do it using the command task. Looks like it's not yet possible to copy or move remote files using an Ansible module. – Christian Berendt Jun 11 '14 at 14:50
  • 4
    Without using the command module, about your only other choice would be to write your own custom module. – Bruce P Jun 11 '14 at 15:05
  • 1
    Probably I'll extend the copy module to also support remote-only copy operations. – Christian Berendt Jun 11 '14 at 15:23
  • 3
    Tagged as correct answer because this is the current way to copy remote files. – Christian Berendt Jun 23 '14 at 11:06
  • 16
    Regarding looking before you leap: is there any reason to not use the `removes` option to the `command` module (documented [here](http://docs.ansible.com/command_module.html))? It seems that that option would make Ansible check first. – Jim Witschey Feb 14 '15 at 20:13
  • 2
    Ansible tracks changes to notify the handler, which makes this solution suboptimal. – boh Oct 06 '15 at 09:01
  • 6
    You do not have to manually check for the file's existence if you use `removes: /path/to/foo` and `creates: /path/to/bar`. @Fonant already mentioned this as comment on another answer, but as this is the accepted one, I want to point it out again. – Michael Trojanek Sep 11 '17 at 16:10
  • 2
    downvote for not using `removes: /path/to/foo` and `creates: /path/to/bar` – Alec Wenzowski Nov 24 '17 at 05:22
  • Although that solution is ansibleish and correct, as you are using a bash command you can also leave it in one task using the following: `test -f file && mv file file.old`. In that way `mv` only would run when the file exists – zaratustra689 Sep 23 '22 at 21:08
124

I have found the creates option in the command module useful. How about this:

- name: Move foo to bar
  command: mv /path/to/foo /path/to/bar
  args:
    creates: /path/to/bar 

I used to do a two task approach using stat like @Bruce P suggests. Now I do this as one task with creates. I think this is a lot clearer.

β.εηοιτ.βε
  • 33,893
  • 13
  • 69
  • 83
Tom Ekberg
  • 2,133
  • 1
  • 13
  • 8
  • 73
    Or, even better: `command: mv /path/to/foo /path/to/bar creates=/path/to/bar removes=/path/to/foo` – Fonant Apr 07 '15 at 15:49
  • As you are using a command, you can also leave it in: `test -f file && mv file file.old` – zaratustra689 Sep 23 '22 at 21:10
  • 1
    This is the best answer if you want to move (but preserve) an original config file/directory out of the way and create or clone a custom one in its place. Bruce P's answer would clobber the new custom file/folder every time. This is also the most elegant solution. Check the example in the docs for better syntax: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/command_module.html – Dave May 27 '23 at 14:40
30
- name: Move the src file to dest
  command: mv /path/to/src /path/to/dest
  args:
    removes: /path/to/src
    creates: /path/to/dest

This runs the mv command only when /path/to/src exists and /path/to/dest does not, so it runs once per host, moves the file, then doesn't run again.

I use this method when I need to move a file or directory on several hundred hosts, many of which may be powered off at any given time. It's idempotent and safe to leave in a playbook.

Earl Ruby
  • 1,263
  • 12
  • 14
10

Another Option that has worked well for me is using the synchronize module . Then remove the original directory using the file module.

Here is an example from the docs:

- synchronize:
    src: /first/absolute/path
    dest: /second/absolute/path
    archive: yes
  delegate_to: "{{ inventory_hostname }}"
Andrew Becker
  • 109
  • 1
  • 2
  • This doesn't work locally in every case because `dest` is accessed through SSH even if the directory is on the same machine. – Kalle Richter Feb 04 '19 at 12:18
10

I know it's a YEARS old topic, but I got frustrated and built a role for myself to do exactly this for an arbitrary list of files. Extend as you see fit:

main.yml

- name: created destination directory
  file:
    path: /path/to/directory
    state: directory
    mode: '0750'
- include_tasks: move.yml
  loop:
    - file1
    - file2
    - file3

move.yml

- name: stat the file
  stat:
    path: {{ item }}
  register: my_file

- name: hard link the file into directory
  file:
    src: /original/path/to/{{ item }}
    dest: /path/to/directory/{{ item }}
    state: hard
  when: my_file.stat.exists

- name: Delete the original file
  file:
    path: /original/path/to/{{ item }}
    state: absent
  when: my_file.stat.exists

Note that hard linking is preferable to copying here, because it inherently preserves ownership and permissions (in addition to not consuming more disk space for a second copy of the file).

Swordgeek
  • 101
  • 1
  • 2
5

This is the way I got it working for me:

  Tasks:
  - name: checking if the file 1 exists
     stat:      
      path: /path/to/foo abc.xts
     register: stat_result

  - name: moving file 1
    command: mv /path/to/foo abc.xts /tmp
    when: stat_result.stat.exists == True

the playbook above, will check if file abc.xts exists before move the file to tmp folder.

eduprado
  • 81
  • 1
  • 4
  • 3
    No need to use `when: stat_result.stat.exists == True`. Just using `when: stat_result.stat.exists` is good enough. – kuttumiah Apr 02 '18 at 22:52
  • I usually use the `== True` because I'm always doing something when the file is not found or `== False`. – eduprado Aug 06 '19 at 15:53
  • According to [Official documentation page of `stat` module](https://docs.ansible.com/ansible/latest/modules/stat_module.html) `exists` property returns a `boolean` value. So, if you only put `when: stat_result.stat.exists` that will satisfy the condition if the file is present which is also identical for `when: stat_result.stat.exists == True` but with more texts and unnecessary conditional check. – kuttumiah Aug 30 '19 at 06:04
5

Another way to achieve this is using file with state: hard.

This is an example I got to work:

- name: Link source file to another destination
  file:
    src: /path/to/source/file
    path: /target/path/of/file
    state: hard

Only tested on localhost (OSX) though, but should work on Linux as well. I can't tell for Windows.

Note that absolute paths are needed. Else it wouldn't let me create the link. Also you can't cross filesystems, so working with any mounted media might fail.

The hardlink is very similar to moving, if you remove the source file afterwards:

- name: Remove old file
  file:
    path: /path/to/source/file
    state: absent

Another benefit is that changes are persisted when you're in the middle of a play. So if someone changes the source, any change is reflected in the target file.

You can verify the number of links to a file via ls -l. The number of hardlinks is shown next to the mode (e.g. rwxr-xr-x 2, when a file has 2 links).

martinczerwi
  • 2,837
  • 23
  • 23
  • 2
    Unfortunately, this will not work for a directory, as hard links are not allowed for directories ((( – Drew Oct 03 '17 at 20:03
  • 1
    This answer makes an assumption about the target system, specifically that both src and dest are on the same partition. This may not be true and therefore this answer should not be used. – jficz Apr 24 '19 at 14:04
4

Bruce wasn't attempting to stat the destination to check whether or not to move the file if it was already there; he was making sure the file to be moved actually existed before attempting the mv.

If your interest, like Tom's, is to only move if the file doesn't already exist, I think we should still integrate Bruce's check into the mix:

- name: stat foo
  stat: path=/path/to/foo
  register: foo_stat

- name: Move foo to bar
  command: creates="path/to/bar" mv /path/to/foo /path/to/bar
  when: foo_stat.stat.exists
gkedge
  • 197
  • 2
  • 11
1

This may seem like overkill, but if you want to avoid using the command module (which I do, because it using command is not idempotent) you can use a combination of copy and unarchive.

  1. Use tar to archive the file(s) you will need. If you think ahead this actually makes sense. You may want a series of files in a given directory. Create that directory with all of the files and archive them in a tar.
  2. Use the unarchive module. When you do that, along with the destination: and remote_src: keyword, you can place copy all of your files to a temporary folder to start with and then unpack them exactly where you want to.
1

On Windows: - name: Move old folder to backup win_command: "cmd.exe /c move /Y {{ sourcePath }} {{ destinationFolderPath }}"

To rename use rename or ren command instead

Vitaly
  • 2,064
  • 19
  • 23
  • Please explain the use of /c and /Y, I was more interested on rename and able to do it using without /Y. I have gone through help topic with cmd /? but didn't make much sense to include /Y here. – user2964808 Nov 19 '20 at 13:10
  • 1
    @user2964808, /c is to have cmd exit and return control, otherwise it will wait for user input – Vitaly Nov 19 '20 at 19:06
  • 1
    @user2964808, /Y is to skip asking for confirmation if file already exists. If you're 100% sure the destination folder is empty, you may skip it – Vitaly Nov 19 '20 at 19:08
0

You can Do It by --

Using Ad Hoc Command

ansible all -m command -a" mv /path/to/foo /path/to/bar"

Or You if you want to do it by using playbook

- name: Move File foo to destination bar
  command: mv /path/to/foo /path/to/bar
Dev pokhariya
  • 336
  • 4
  • 16
0

to copy/move/rename any file we can use shell or command module

  • name: copy or mv within remote host command: cp /path/of/the/file/with/name /path/of/the/destination/directory/

while giving source make sure name of the file is last with no slash(/) and destination ends with slash(/)

  • name: rename within remote host shell: chdir=/path/ mv file_name new_file_name

command and shell and be used interchangeably.

-3
- name: Example
  hosts: localhost
  become: yes

  tasks:
  - name: checking if a file exists
    stat:
      path: "/projects/challenge/simplefile.txt"
    register: file_data
  
  - name: move the file if file exists
    copy: 
      src: /projects/challenge/simplefile.txt
      dest: /home/user/test
    when: file_data.stat.exists

  - name: report a missing file
    debug: 
      msg: "the file or directory doesn't exist"
    when: not file_data.stat.exists
m4n0
  • 29,823
  • 27
  • 76
  • 89
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 25 '21 at 17:51