220

The below code only deletes the first file it gets inside the web dir. I want to remove all the files and folders inside the web directory and retain the web directory. How can I do that?

- name: remove web dir contents
    file: path='/home/mydata/web/{{ item }}' state=absent
    with_fileglob:
      - /home/mydata/web/*

Note: I've tried rm -rf using command and shell, but they don't work. Perhaps I am using them wrongly.

Any help in the right direction will be appreciated.

I am using ansible 2.1.0.0

Esqarrouth
  • 38,543
  • 21
  • 161
  • 168
Abbas
  • 3,144
  • 2
  • 25
  • 45

22 Answers22

194
- name: Delete content & directory
  ansible.builtin.file:
    state: absent
    path: /home/mydata/web/

Note: this will delete the directory too.

aymericbeaumet
  • 6,853
  • 2
  • 37
  • 50
Mohan Kumar P
  • 3,122
  • 1
  • 14
  • 17
  • @Abbas You should use YAML-style arguments intead of Ansible's hacky key=value format. Like this: `file: {state: absent, path: "{{ artifact_path }}"}`. (You can put the arguments on multiple lines, but I can't show that in a comment.) – augurar Sep 08 '16 at 05:36
  • 147
    The OP (and myself) want a solution that will delete the contents of the folder BUT NOT the folder itself. This solution deletes the contents AND the folder itself. – stochastic Feb 25 '17 at 00:38
  • 47
    How did this get an upvote? This deletes the directory too. – deppfx Mar 22 '17 at 22:54
  • 1
    I can't up-vote the solution for the reasons above, but I wasn't looking to preserve the directory for my purposes, and having difficulty understanding what I was missing -- this was the perfect solution for me. – Cognitiaclaeves Apr 25 '17 at 21:16
  • 22
    Has anyone had the above code fail on them with artifact_path being null? This feels like it could be susceptible to one of those great `rm -rf /` moments in history – ted-k42 May 08 '17 at 22:45
  • 4
    @ted-k42 I'd do something like this just to be safe: `when: artifact_path is defined and artifact_path != ""` – bmaupin May 30 '17 at 14:32
  • 1
    Not idempotent. That's Rule 1 of any configuration management task. – Carlos Konstanski May 03 '18 at 19:50
  • 1
    This will delete the directory too, thus not a solution to Empty directory. – Omar BISTAMI Jul 29 '19 at 14:47
  • 1
    This answer misses the point. For example, if there is a file-system mounted under eg /home/mydata/web and you want to remove everything in the filesystem, this won't work. – Mali Remorker Jun 04 '21 at 12:31
  • this solution is the simplest one here. If you want to preserve the directory, it's easy to just re-create it after the removal: ``` name: re-create directory file: state: directory path: /home/mydata/web/ ``` – jficz Apr 22 '22 at 10:23
98

Remove the directory (basically a copy of https://stackoverflow.com/a/38201611/1695680), Ansible does this operation with rmtree under the hood.

- name: remove files and directories
  ansible.builtin.file:
    state: "{{ item }}"
    path: "/srv/deleteme/"
    owner: 1000  # set your owner, group, and mode accordingly
    group: 1000
    mode: '0777'
  with_items:
    - absent
    - directory

If you don't have the luxury of removing the whole directory and recreating it, you can scan it for files, (and directories), and delete them one by one. Which will take a while. You probably want to make sure you have [ssh_connection]\npipelining = True in your ansible.cfg on.

- block:
  - name: 'collect files'
    find:
      paths: "/srv/deleteme/"
      hidden: True
      recurse: True
      # file_type: any  # Added in ansible 2.3
    register: collected_files

  - name: 'collect directories'
    find:
      paths: "/srv/deleteme/"
      hidden: True
      recurse: True
      file_type: directory
    register: collected_directories

  - name: remove collected files and directories
    file:
      path: "{{ item.path }}"
      state: absent
    with_items: >
      {{
        collected_files.files
        + collected_directories.files
      }}
aymericbeaumet
  • 6,853
  • 2
  • 37
  • 50
ThorSummoner
  • 16,657
  • 15
  • 135
  • 147
  • 16
    The first task is the most elegant solution I've seen. Note the comments elsewhere that there's a long-running feature request to add `state=empty` – William Turrell Mar 23 '18 at 21:04
92

Using shell module (idempotent too):

- shell: /bin/rm -rf /home/mydata/web/*

If there are dot/hidden files:

- shell: /bin/rm -rf /home/mydata/web/* /home/mydata/web/.*

Cleanest solution if you don't care about creation date and owner/permissions:

- file: path=/home/mydata/web state=absent
- file: path=/home/mydata/web state=directory
helloV
  • 50,176
  • 7
  • 137
  • 145
  • 9
    Works but didn't remove files starting with . like .htaccess – Abbas Jul 05 '16 at 12:34
  • 1
    Note that this might change your permissions/owner unless you set the explicitly during creation moment. – Tom Raganowicz Mar 05 '19 at 06:25
  • Using **shell** you'll get a warning of using the file module with state=absent instead of shell rm. To avoid it just add _args: warn: false_ – Rodrigo Aug 20 '19 at 18:38
  • 1
    This is actually the fastest and most readable answer in this post, even if it's not "native Ansible". You can use `rm -rf {{ path }}/.[!.]* {{ path }}/*` if you want to get really fancy and delete files with leading dots too. – Ocab19 Oct 19 '20 at 14:55
  • 6
    @Ocab19 rm -rf [variable]/* is a risky operation. I do not recommend it. What if [variable] is null. That becomes rm -rf /*. Although it is prevented for many servers, it could cause a disaster. – Kang Andrew Nov 02 '21 at 00:48
53

I really didn't like the rm solution, also ansible gives you warnings about using rm. So here is how to do it without the need of rm and without ansible warnings.

- hosts: all
  tasks:
  - name: Ansible delete file glob
    find:
      paths: /etc/Ansible
      patterns: "*.txt"
    register: files_to_delete

  - name: Ansible remove file glob
    file:
      path: "{{ item.path }}"
      state: absent
    with_items: "{{ files_to_delete.files }}"

source: http://www.mydailytutorials.com/ansible-delete-multiple-files-directories-ansible/

Cris
  • 891
  • 7
  • 13
  • 3
    yet the best solution for the question !! – Vsegar Jun 06 '20 at 10:05
  • 2
    That will delete folder `item.path` as well – JackTheKnife Oct 06 '20 at 19:22
  • At least in the present version it will not delete `item.path` folder. – kap Dec 08 '20 at 16:17
  • 1
    Thanks! For cleaning out a directory when some items inside it might be directories, or start with a `.`, I needed to add `file_type: any` and `hidden: true` to the `find:`. [`excludes`](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/find_module.html#parameter-excludes) was also handy. – andrewdotn Aug 09 '21 at 19:45
18

try the below command, it should work

- shell: ls -1 /some/dir
  register: contents

- file: path=/some/dir/{{ item }} state=absent
  with_items: {{ contents.stdout_lines }}
Marcin Orlowski
  • 72,056
  • 11
  • 123
  • 141
  • 4
    you missed to correctly escape last line with {{, }} to obtain `with_items: "{{ contents.stdout_lines }}"` – Valerio Crini Mar 29 '17 at 15:00
  • 2
    It’s dangerous to use `ls`’ output to get filenames because it doesn’t correctly print filenames with special characters such as newlines. – bfontaine Oct 20 '17 at 13:14
15

That's what I come up with:

- name: Get directory listing
  find:
    path: "{{ directory }}" 
    file_type: any
    hidden: yes
  register: directory_content_result

- name: Remove directory content
  file:
    path: "{{ item.path }}" 
    state: absent
  with_items: "{{ directory_content_result.files }}" 
  loop_control:
    label: "{{ item.path }}" 

First, we're getting directory listing with find, setting

  • file_type to any, so we wouldn't miss nested directories and links
  • hidden to yes, so we don't skip hidden files
  • also, do not set recurse to yes, since it is not only unnecessary, but may increase execution time.

Then, we go through that list with file module. It's output is a bit verbose, so loop_control.label will help us with limiting output (found this advice here).


But I found previous solution to be somewhat slow, since it iterates through the content, so I went with:

- name: Get directory stats
  stat:
    path: "{{ directory }}"
  register: directory_stat

- name: Delete directory
  file:
    path: "{{ directory }}"
    state: absent

- name: Create directory
  file:
    path: "{{ directory }}"
    state: directory
    owner: "{{ directory_stat.stat.pw_name }}"
    group: "{{ directory_stat.stat.gr_name }}"
    mode: "{{ directory_stat.stat.mode }}"
  • get directory properties with the stat
  • delete directory
  • recreate directory with the same properties.

That was enough for me, but you can add attributes as well, if you want.

Alexander
  • 3,129
  • 2
  • 19
  • 33
7

Using file glob also it will work. There is some syntax error in the code you posted. I have modified and tested this should work.

- name: remove web dir contents
  file:
    path: "{{ item }}"
    state: absent
  with_fileglob:
    - "/home/mydata/web/*"
Deepali Mittal
  • 996
  • 13
  • 20
5

While Ansible is still debating to implement state = empty https://github.com/ansible/ansible-modules-core/issues/902

my_folder: "/home/mydata/web/"
empty_path: "/tmp/empty"


- name: "Create empty folder for wiping."
  file:
    path: "{{ empty_path }}" 
    state: directory

- name: "Wipe clean {{ my_folder }} with empty folder hack."
  synchronize:
    mode: push

    #note the backslash here
    src: "{{ empty_path }}/" 

    dest: "{{ nl_code_path }}"
    recursive: yes
    delete: yes
  delegate_to: "{{ inventory_hostname }}"

Note though, with synchronize you should be able to sync your files (with delete) properly anyway.

Marcin
  • 148
  • 2
  • 4
5

Created an overall rehauled and fail-safe implementation from all comments and suggestions:

# collect stats about the dir
- name: check directory exists
  stat:
    path: '{{ directory_path }}'
  register: dir_to_delete

# delete directory if condition is true
- name: purge {{directory_path}}
  file:
    state: absent
    path: '{{ directory_path  }}'
  when: dir_to_delete.stat.exists and dir_to_delete.stat.isdir

# create directory if deleted (or if it didn't exist at all)
- name: create directory again
  file:
    state: directory
    path: '{{ directory_path }}'
  when: dir_to_delete is defined or dir_to_delete.stat.exist == False
Markus
  • 1,887
  • 18
  • 23
  • I think you're missing the `register` for `plugin_dir_deleted`, right? – Bob Vork Jul 09 '18 at 07:32
  • thanks for pointing this out. I had this piece of code taken out of one of my playbooks and made it more generic in terms of naming but forget to replace two variable names – Markus Jul 09 '18 at 07:51
  • 1
    Good stuff, but I think the state in the last task needs to be set to "directory" rather than "present". – Andrew Allaire Aug 06 '18 at 22:02
  • 1
    You should use your stat result to preserve permissions: owner from pw_name, group from gr_name, and mode from mode. – DylanYoung Oct 26 '18 at 19:37
  • tried `owner: dir_to_delete.stat.pw_name, group: dir_to_delete.stat.gr_name mode: dir_to_delete.stat.mode` but it fails on me with my current Ansible version :( – Markus May 08 '19 at 12:30
5

Following up on the most upvoted answer here (which I cannot edit since "edit queue is full"):

- name: Delete content & directory
  file:
    state: absent
    path: /home/mydata/web/

- name: Re-create the directory
  file:
    state: directory
    path: /home/mydata/web/
jficz
  • 271
  • 3
  • 8
  • 2
    Note that you might need to add `owner` and/or `group` and/or `mode` to re-create the directory with correct ownership and permissions. – skazi Jul 07 '22 at 18:21
  • true, ownership, acls,selinux labels, etc., need to be updated as well if needed – jficz Oct 18 '22 at 12:20
3

Below code worked for me :

- name: Get directory listing
  become: yes
  find:
    paths: /applications/cache
    patterns: '*'
    hidden: yes
  register: directory_content_result

- name: Remove directory content
  become: yes
  file:
    path: "{{ item.path }}"
    state: absent
  with_items: "{{ directory_content_result.files }}"
Manju
  • 31
  • 2
2

There is an issue open with respect to this.

For now, the solution works for me: create a empty folder locally and synchronize it with the remote one.

Here is a sample playbook:

- name: "Empty directory"
  hosts: *
  tasks:
    - name: "Create an empty directory (locally)"
      local_action:
        module: file
        state: directory
        path: "/tmp/empty"

    - name: Empty remote directory
      synchronize:
        src: /tmp/empty/
        dest: /home/mydata/web/
        delete: yes
        recursive: yes
Cans
  • 434
  • 3
  • 11
w1100n
  • 1,460
  • 2
  • 17
  • 24
1

I want to make sure that the find command only deletes everything inside the directory and leave the directory intact because in my case the directory is a filesystem. The system will generate an error when trying to delete a filesystem but that is not a nice option. Iam using the shell option because that is the only working option I found so far for this question.

What I did:

Edit the hosts file to put in some variables:

[all:vars]
COGNOS_HOME=/tmp/cognos
find=/bin/find

And create a playbook:

- hosts: all
  tasks:
  - name: Ansible remove files
    shell: "{{ find }} {{ COGNOS_HOME }} -xdev -mindepth 1 -delete"

This will delete all files and directories in the COGNOS_HOME variable directory/filesystem. The "-mindepth 1" option makes sure that the current directory will not be touched.

Tjerk Maij
  • 19
  • 1
1

I have written an custom ansible module to cleanup files based on multiple filters like age, timestamp, glob patterns, etc.

It is also compatible with ansible older versions. It can be found here.

Here is an example:

- cleanup_files:
  path_pattern: /tmp/*.log
  state: absent
  excludes:
    - foo*
    - bar*
double-beep
  • 5,031
  • 17
  • 33
  • 41
Rahul
  • 11
  • 2
0

Just a small cleaner copy & paste template of ThorSummoners answer, if you are using Ansible >= 2.3 (distinction between files and dirs not necessary anymore.)

- name: Collect all fs items inside dir
  find:
    path: "{{ target_directory_path }}"
    hidden: true
    file_type: any
  changed_when: false
  register: collected_fsitems
- name: Remove all fs items inside dir
  file:
    path: "{{ item.path }}"
    state: absent
  with_items: "{{ collected_fsitems.files }}"
  when: collected_fsitems.matched|int != 0
Enno Gröper
  • 4,391
  • 1
  • 27
  • 33
0

Isn't it that simple ... tested working ..

eg.

---
- hosts: localhost
  vars:
     cleandir: /var/lib/cloud/
  tasks:
   - shell: ls -a -I '.' -I '..' {{ cleandir }}
     register: ls2del
     ignore_errors: yes
   - name: Cleanup {{ cleandir }}
     file:
       path: "{{ cleandir }}{{ item }}"
       state: absent
     with_items: "{{ ls2del.stdout_lines }}"
Shirish Shukla
  • 319
  • 2
  • 6
  • Doesn't work for me. Shows Green output for the task meaning nothing is changed and of course my files are still there in that folder. Can you show me your output ? – objectNotFound Dec 16 '20 at 08:17
  • yup thats what I thought ... so I ended up using the ansible "find" module to first find all files (with path) to delete and registered it to a var ( in your case you used ls2del ). So now the last line in your cleanup task would change to loop: "{{ ls2del['files'] }}" and the path would be: "{{ item['path'] }}" – objectNotFound Dec 18 '20 at 02:24
  • yes, beauty of ansible is we can have many ways of solution. In find module to include hidden files as well .. use "hidden: yes" . – Shirish Shukla Dec 19 '20 at 06:45
0
- name: Files to delete search
  find:
    paths: /home/mydata/web/
    file_type: any
  register: files_to_delete

- name: Deleting files to delete
  file:
    path: '{{ item.path }}'
    state: absent
  with_items: "{{ files_to_delete.files }}"
Eldar
  • 1
  • 1
0

I like the following solution:

- name: remove web dir contents
  command:
    cmd: "find . -path '*/*' -delete -print"
    chdir: "/home/mydata/web/"
  register: web_files_list
  changed_when: web_files_list.stdout | length > 0

because it is:

  • simple
  • idempotent
  • fast
0

This solution worked for me and its a rather simple one, just had to read the man page of find command in linux.

- name: Remove contents of a directory
  hosts: localhost
  tasks:
    - name: Find files and directories within the directory
      shell:
        cmd: "find path_to_your_directory -mindepth 1 -delete"

ref to the man page of find:

-mindepth levels Do not apply any tests or actions at levels less than levels (a non- negative integer). Using -mindepth 1 means process all files except the starting-points.

Sherry
  • 1
  • 1
0

I follow this file module its worst to try something from the official docs: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/file_module.html

Ayoung
  • 23
  • 4
-1

Assuming you are always in Linux, try the find cmd.

- name: Clean everything inside {{ item }}
  shell: test -d {{ item }} && find {{ item }} -path '{{ item }}/*' -prune -exec rm -rf {} \;
  with_items: [/home/mydata/web]

This should wipe out files/folders/hidden under /home/mydata/web

-2
  - name: delete old data and clean cache
    file:
      path: "{{ item[0] }}" 
      state: "{{ item[1] }}"
    with_nested:
      - [ "/data/server/{{ app_name }}/webapps/", "/data/server/{{ app_name }}/work/" ]
      - [ "absent", "directory" ]
    ignore_errors: yes
General Grievance
  • 4,555
  • 31
  • 31
  • 45
lanni654321
  • 1,019
  • 11
  • 7