diff --git a/changelogs/fragments/569_keep_mountpoint.yml b/changelogs/fragments/569_keep_mountpoint.yml new file mode 100644 index 0000000000..3536263fc8 --- /dev/null +++ b/changelogs/fragments/569_keep_mountpoint.yml @@ -0,0 +1,3 @@ +--- +minor_changes: +- keep_mountpoint - added keep_mountpoint option with default value false. If set to true keep_mountpoint changes the behaviour of state\: absent by keeping the mountpoint. diff --git a/plugins/modules/mount.py b/plugins/modules/mount.py index fe9faff569..d8e01b96fd 100644 --- a/plugins/modules/mount.py +++ b/plugins/modules/mount.py @@ -87,7 +87,8 @@ real source. V(absent) does not unmount recursively, and the module will fail if multiple devices are mounted on the same mount point. Using V(absent) with a mount point that is not registered in the I(fstab) has - no effect, use V(unmounted) instead. + no effect, use V(unmounted) instead. You can set O(keep_mountpoint) to + True to keep the mountpoint. - V(remounted) specifies that the device will be remounted for when you want to force a refresh on the mount itself (added in 2.9). This will always return RV(ignore:changed=true). If O(opts) is set, the options will be @@ -132,6 +133,16 @@ the original file back if you somehow clobbered it incorrectly. type: bool default: false + keep_mountpoint: + description: + - Change the default behaviour of state=absent by keeping the mountpoint + - With keep_mountpoint=true, state=absent is like unmounted plus the + fstab update. + - Use it if you care about finding original mountpoint content without failing + and want to remove the entry in fstab. If you have no entry to clean in + fstab you can use state=unmounted + type: bool + default: false notes: - As of Ansible 2.3, the O(name) option has been changed to O(path) as default, but O(name) still works as well. @@ -175,6 +186,23 @@ path: /tmp/mnt-pnt state: remounted +# The following will fail on first run +# if /home/mydir is not empty after unmounting, +# though unmount and remove from fstab are successfull. +# It will be successfull on subsequent runs (already unmounted). +- name: Unmount and remove from fstab, then if unmount was necessary try to remove mountpoint /home/mydir + ansible.posix.mount: + path: /home/mydir + state: absent +# The following will not fail on first run +# if /home/mydir is not empty after unmounting. +# It will leave /home/mydir and its content (if any) after unmounting. +- name: Unmount and remove from fstab, but keep /home/mydir + ansible.posix.mount: + path: /home/mydir + state: absent + keep_mountpoint: true + # The following will not save changes to fstab, and only be temporary until # a reboot, or until calling "state: unmounted" followed by "state: mounted" # on the same "path" @@ -779,6 +807,7 @@ def main(): src=dict(type='path'), backup=dict(type='bool', default=False), state=dict(type='str', required=True, choices=['absent', 'absent_from_fstab', 'mounted', 'present', 'unmounted', 'remounted', 'ephemeral']), + keep_mountpoint=dict(type='bool', default=False), ), supports_check_mode=True, required_if=( @@ -899,7 +928,7 @@ def main(): module.fail_json( msg="Error unmounting %s: %s" % (name, msg)) - if os.path.exists(name): + if os.path.exists(name) and module.params['keep_mountpoint'] is False: try: os.rmdir(name) except (OSError, IOError) as e: diff --git a/tests/integration/targets/mount/tasks/main.yml b/tests/integration/targets/mount/tasks/main.yml index 15c6e897d7..09b315f4d0 100644 --- a/tests/integration/targets/mount/tasks/main.yml +++ b/tests/integration/targets/mount/tasks/main.yml @@ -789,3 +789,85 @@ loop: - /tmp/myfs.img - /tmp/myfs + +- name: Block to test keep_mountpoint option + block: + - name: Create the mount point + ansible.builtin.file: + state: directory + path: '/tmp/myfs' + mode: '0755' + + - name: Create empty file for FS aaa + community.general.filesize: + path: /tmp/myfs.img + size: 20M + + - name: Format FS bbb + community.general.filesystem: + fstype: xfs + dev: /tmp/myfs.img + + - name: Put data in the mount point before mounting + ansible.builtin.copy: + content: 'Testing + This is the data before mounting + ' + dest: '/tmp/myfs/test_file' + mode: '0644' + register: file_before_info + + - name: Mount with fstab + ansible.posix.mount: + path: '/tmp/myfs' + fstype: xfs + state: mounted + src: '/tmp/myfs.img' + + - name: Check data disappears - stat data + ansible.builtin.stat: + path: '/tmp/myfs/test_file' + register: file_stat_after_mount + - name: Check data disappears - file does not exist + ansible.builtin.assert: + that: + - file_stat_after_mount['stat']['exists'] == false + - name: Put data in the mount point after mounting + ansible.builtin.copy: + content: 'Testing + This is the data updated after mounting + ' + dest: '/tmp/myfs/test_file' + mode: '0644' + register: file_after_info + - name: Umount with keep_mountpoint + ansible.posix.mount: + path: '/tmp/myfs' + fstype: xfs + state: absent + keep_mountpoint: true + - name: Check original data reappears - stat data + ansible.builtin.stat: + path: '/tmp/myfs/test_file' + register: file_stat_after_umount + - name: Check original data reappears - compare checksums + ansible.builtin.assert: + that: + - file_stat_after_umount['stat']['checksum'] == file_before_info['checksum'] + always: + - name: Remove the first test file + ansible.builtin.file: + path: /tmp/myfs/test_file + state: absent + - name: Unmount FS + ansible.posix.mount: + path: /tmp/myfs + state: absent + - name: Remove the test FS and the second test file + ansible.builtin.file: + path: '{{ item }}' + state: absent + loop: + - /tmp/myfs/test_file + - /tmp/myfs.img + - /tmp/myfs