Server IP : 85.214.239.14 / Your IP : 3.17.165.196 Web Server : Apache/2.4.62 (Debian) System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Tue Jan 9 19:45:01 MSK 2024 x86_64 User : www-data ( 33) PHP Version : 7.4.18 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : OFF | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /proc/2/root/lib/python3/dist-packages/ansible_collections/ansible/posix/plugins/modules/ |
Upload File : |
#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright: (c) 2012, Red Hat, inc # Written by Seth Vidal # based on the mount modules from salt and puppet # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = r''' --- module: mount short_description: Control active and configured mount points description: - This module controls active and configured mount points in C(/etc/fstab). author: - Ansible Core Team - Seth Vidal (@skvidal) version_added: "1.0.0" options: path: description: - Path to the mount point (e.g. C(/mnt/files)). - Before Ansible 2.3 this option was only usable as I(dest), I(destfile) and I(name). type: path required: true aliases: [ name ] src: description: - Device (or NFS volume, or something else) to be mounted on I(path). - Required when I(state) set to C(present), C(mounted) or C(ephemeral). type: path fstype: description: - Filesystem type. - Required when I(state) is C(present), C(mounted) or C(ephemeral). type: str opts: description: - Mount options (see fstab(5), or vfstab(4) on Solaris). type: str dump: description: - Dump (see fstab(5)). - Note that if set to C(null) and I(state) set to C(present), it will cease to work and duplicate entries will be made with subsequent runs. - Has no effect on Solaris systems or when used with C(ephemeral). type: str default: '0' passno: description: - Passno (see fstab(5)). - Note that if set to C(null) and I(state) set to C(present), it will cease to work and duplicate entries will be made with subsequent runs. - Deprecated on Solaris systems. Has no effect when used with C(ephemeral). type: str default: '0' state: description: - If C(mounted), the device will be actively mounted and appropriately configured in I(fstab). If the mount point is not present, the mount point will be created. - If C(unmounted), the device will be unmounted without changing I(fstab). - C(present) only specifies that the device is to be configured in I(fstab) and does not trigger or require a mount. - C(ephemeral) only specifies that the device is to be mounted, without changing I(fstab). If it is already mounted, a remount will be triggered. This will always return changed=True. If the mount point I(path) has already a device mounted on, and its source is different than I(src), the module will fail to avoid unexpected unmount or mount point override. If the mount point is not present, the mount point will be created. The I(fstab) is completely ignored. This option is added in version 1.5.0. - C(absent) specifies that the device mount's entry will be removed from I(fstab) and will also unmount the device and remove the mount point. - C(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 changed=true. If I(opts) is set, the options will be applied to the remount, but will not change I(fstab). Additionally, if I(opts) is set, and the remount command fails, the module will error to prevent unexpected mount changes. Try using C(mounted) instead to work around this issue. C(remounted) expects the mount point to be present in the I(fstab). To remount a mount point not registered in I(fstab), use C(ephemeral) instead, especially with BSD nodes. - C(absent_from_fstab) specifies that the device mount's entry will be removed from I(fstab). This option does not unmount it or delete the mountpoint. type: str required: true choices: [ absent, absent_from_fstab, mounted, present, unmounted, remounted, ephemeral ] fstab: description: - File to use instead of C(/etc/fstab). - You should not use this option unless you really know what you are doing. - This might be useful if you need to configure mountpoints in a chroot environment. - OpenBSD does not allow specifying alternate fstab files with mount so do not use this on OpenBSD with any state that operates on the live filesystem. - This parameter defaults to /etc/fstab or /etc/vfstab on Solaris. - This parameter is ignored when I(state) is set to C(ephemeral). type: str boot: description: - Determines if the filesystem should be mounted on boot. - Only applies to Solaris and Linux systems. - For Solaris systems, C(true) will set C(yes) as the value of mount at boot in I(/etc/vfstab). - For Linux, FreeBSD, NetBSD and OpenBSD systems, C(false) will add C(noauto) to mount options in I(/etc/fstab). - To avoid mount option conflicts, if C(noauto) specified in C(opts), mount module will ignore C(boot). - This parameter is ignored when I(state) is set to C(ephemeral). type: bool default: true backup: description: - Create a backup file including the timestamp information so you can get the original file back if you somehow clobbered it incorrectly. type: bool default: false notes: - As of Ansible 2.3, the I(name) option has been changed to I(path) as default, but I(name) still works as well. - Using C(remounted) with I(opts) set may create unexpected results based on the existing options already defined on mount, so care should be taken to ensure that conflicting options are not present before hand. ''' EXAMPLES = r''' # Before 2.3, option 'name' was used instead of 'path' - name: Mount DVD read-only ansible.posix.mount: path: /mnt/dvd src: /dev/sr0 fstype: iso9660 opts: ro,noauto state: present - name: Mount up device by label ansible.posix.mount: path: /srv/disk src: LABEL=SOME_LABEL fstype: ext4 state: present - name: Mount up device by UUID ansible.posix.mount: path: /home src: UUID=b3e48f45-f933-4c8e-a700-22a159ec9077 fstype: xfs opts: noatime state: present - name: Unmount a mounted volume ansible.posix.mount: path: /tmp/mnt-pnt state: unmounted - name: Remount a mounted volume ansible.posix.mount: path: /tmp/mnt-pnt state: remounted # 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" - name: Remount a mounted volume and append exec to the existing options ansible.posix.mount: path: /tmp state: remounted opts: exec - name: Mount and bind a volume ansible.posix.mount: path: /system/new_volume/boot src: /boot opts: bind state: mounted fstype: none - name: Mount an NFS volume ansible.posix.mount: src: 192.168.1.100:/nfs/ssd/shared_data path: /mnt/shared_data opts: rw,sync,hard state: mounted fstype: nfs - name: Mount NFS volumes with noauto according to boot option ansible.posix.mount: src: 192.168.1.100:/nfs/ssd/shared_data path: /mnt/shared_data opts: rw,sync,hard boot: false state: mounted fstype: nfs - name: Mount ephemeral SMB volume ansible.posix.mount: src: //192.168.1.200/share path: /mnt/smb_share opts: "rw,vers=3,file_mode=0600,dir_mode=0700,dom={{ ad_domain }},username={{ ad_username }},password={{ ad_password }}" fstype: cifs state: ephemeral ''' import errno import os import platform from ansible.module_utils.basic import AnsibleModule from ansible_collections.ansible.posix.plugins.module_utils.mount import ismount from ansible.module_utils.six import iteritems from ansible.module_utils._text import to_bytes, to_native from ansible.module_utils.parsing.convert_bool import boolean def write_fstab(module, lines, path): if module.params['backup']: backup_file = module.backup_local(path) else: backup_file = "" fs_w = open(path, 'w') for l in lines: fs_w.write(l) fs_w.flush() fs_w.close() return backup_file def _escape_fstab(v): """Escape invalid characters in fstab fields. space (040) ampersand (046) backslash (134) """ if isinstance(v, int): return v else: return ( v. replace('\\', '\\134'). replace(' ', '\\040'). replace('&', '\\046')) def set_mount(module, args): """Set/change a mount point location in fstab.""" name, backup_lines, changed = _set_mount_save_old(module, args) return name, changed def _set_mount_save_old(module, args): """Set/change a mount point location in fstab. Save the old fstab contents.""" to_write = [] old_lines = [] exists = False changed = False escaped_args = dict([(k, _escape_fstab(v)) for k, v in iteritems(args) if k != 'warnings']) new_line = '%(src)s %(name)s %(fstype)s %(opts)s %(dump)s %(passno)s\n' if platform.system() == 'SunOS': new_line = ( '%(src)s - %(name)s %(fstype)s %(passno)s %(boot)s %(opts)s\n') for line in open(args['fstab'], 'r').readlines(): # Append newline if the line in fstab does not finished with newline. if not line.endswith('\n'): line += '\n' old_lines.append(line) if not line.strip(): to_write.append(line) continue if line.strip().startswith('#'): to_write.append(line) continue fields = line.split() # Check if we got a valid line for splitting # (on Linux the 5th and the 6th field is optional) if ( platform.system() == 'SunOS' and len(fields) != 7 or platform.system() == 'Linux' and len(fields) not in [4, 5, 6] or platform.system() not in ['SunOS', 'Linux'] and len(fields) != 6): to_write.append(line) continue ld = {} if platform.system() == 'SunOS': ( ld['src'], dash, ld['name'], ld['fstype'], ld['passno'], ld['boot'], ld['opts'] ) = fields else: fields_labels = ['src', 'name', 'fstype', 'opts', 'dump', 'passno'] # The last two fields are optional on Linux so we fill in default values ld['dump'] = 0 ld['passno'] = 0 # Fill in the rest of the available fields for i, field in enumerate(fields): ld[fields_labels[i]] = field # Check if we found the correct line if ( ld['name'] != escaped_args['name'] or ( # In the case of swap, check the src instead 'src' in args and ld['name'] == 'none' and ld['fstype'] == 'swap' and ld['src'] != args['src'])): to_write.append(line) continue # If we got here we found a match - let's check if there is any # difference exists = True args_to_check = ('src', 'fstype', 'opts', 'dump', 'passno') if platform.system() == 'SunOS': args_to_check = ('src', 'fstype', 'passno', 'boot', 'opts') for t in args_to_check: if ld[t] != escaped_args[t]: ld[t] = escaped_args[t] changed = True if changed: to_write.append(new_line % ld) else: to_write.append(line) if not exists: to_write.append(new_line % escaped_args) changed = True if changed and not module.check_mode: args['backup_file'] = write_fstab(module, to_write, args['fstab']) return (args['name'], old_lines, changed) def unset_mount(module, args): """Remove a mount point from fstab.""" to_write = [] changed = False escaped_name = _escape_fstab(args['name']) for line in open(args['fstab'], 'r').readlines(): if not line.strip(): to_write.append(line) continue if line.strip().startswith('#'): to_write.append(line) continue # Check if we got a valid line for splitting if ( platform.system() == 'SunOS' and len(line.split()) != 7 or platform.system() != 'SunOS' and len(line.split()) != 6): to_write.append(line) continue ld = {} if platform.system() == 'SunOS': ( ld['src'], dash, ld['name'], ld['fstype'], ld['passno'], ld['boot'], ld['opts'] ) = line.split() else: ( ld['src'], ld['name'], ld['fstype'], ld['opts'], ld['dump'], ld['passno'] ) = line.split() if ( ld['name'] != escaped_name or ( # In the case of swap, check the src instead 'src' in args and ld['name'] == 'none' and ld['fstype'] == 'swap' and ld['src'] != args['src'])): to_write.append(line) continue # If we got here we found a match - continue and mark changed changed = True if changed and not module.check_mode: write_fstab(module, to_write, args['fstab']) return (args['name'], changed) def _set_fstab_args(fstab_file): result = [] if ( fstab_file and fstab_file != '/etc/fstab' and platform.system().lower() != 'sunos'): if platform.system().lower().endswith('bsd'): result.append('-F') else: result.append('-T') result.append(fstab_file) return result def _set_ephemeral_args(args): result = [] # Set fstype switch according to platform. SunOS/Solaris use -F if platform.system().lower() == 'sunos': result.append('-F') else: result.append('-t') result.append(args['fstype']) # Even if '-o remount' is already set, specifying multiple -o is valid if args['opts'] != 'defaults': result += ['-o', args['opts']] result.append(args['src']) return result def mount(module, args): """Mount up a path or remount if needed.""" mount_bin = module.get_bin_path('mount', required=True) name = args['name'] cmd = [mount_bin] if platform.system().lower() == 'openbsd': # Use module.params['fstab'] here as args['fstab'] has been set to the # default value. if module.params['fstab'] is not None: module.fail_json( msg=( 'OpenBSD does not support alternate fstab files. Do not ' 'specify the fstab parameter for OpenBSD hosts')) else: if module.params['state'] != 'ephemeral': cmd += _set_fstab_args(args['fstab']) if module.params['state'] == 'ephemeral': cmd += _set_ephemeral_args(args) cmd += [name] rc, out, err = module.run_command(cmd) if rc == 0: return 0, '' else: return rc, out + err def umount(module, path): """Unmount a path.""" umount_bin = module.get_bin_path('umount', required=True) cmd = [umount_bin, path] rc, out, err = module.run_command(cmd) if rc == 0: return 0, '' else: return rc, out + err def remount(module, args): """Try to use 'remount' first and fallback to (u)mount if unsupported.""" mount_bin = module.get_bin_path('mount', required=True) cmd = [mount_bin] # Multiplatform remount opts if platform.system().lower().endswith('bsd'): if module.params['state'] == 'remounted' and args['opts'] != 'defaults': cmd += ['-u', '-o', args['opts']] else: cmd += ['-u'] else: if module.params['state'] == 'remounted' and args['opts'] != 'defaults': cmd += ['-o', 'remount,' + args['opts']] else: cmd += ['-o', 'remount'] if platform.system().lower() == 'openbsd': # Use module.params['fstab'] here as args['fstab'] has been set to the # default value. if module.params['fstab'] is not None: module.fail_json( msg=( 'OpenBSD does not support alternate fstab files. Do not ' 'specify the fstab parameter for OpenBSD hosts')) else: if module.params['state'] != 'ephemeral': cmd += _set_fstab_args(args['fstab']) if module.params['state'] == 'ephemeral': cmd += _set_ephemeral_args(args) cmd += [args['name']] out = err = '' try: if module.params['state'] != 'ephemeral' and platform.system().lower().endswith('bsd'): # Note: Forcing BSDs to do umount/mount due to BSD remount not # working as expected (suspect bug in the BSD mount command) # Interested contributor could rework this to use mount options on # the CLI instead of relying on fstab # https://github.com/ansible/ansible-modules-core/issues/5591 # Note: this does not affect ephemeral state as all options # are set on the CLI and fstab is expected to be ignored. rc = 1 else: rc, out, err = module.run_command(cmd) except Exception: rc = 1 msg = '' if rc != 0: msg = out + err if module.params['state'] == 'remounted' and args['opts'] != 'defaults': module.fail_json( msg=( 'Options were specified with remounted, but the remount ' 'command failed. Failing in order to prevent an ' 'unexpected mount result. Try replacing this command with ' 'a "state: unmounted" followed by a "state: mounted" ' 'using the full desired mount options instead.')) rc, msg = umount(module, args['name']) if rc == 0: rc, msg = mount(module, args) return rc, msg # Note if we wanted to put this into module_utils we'd have to get permission # from @jupeter -- https://github.com/ansible/ansible-modules-core/pull/2923 # @jtyr -- https://github.com/ansible/ansible-modules-core/issues/4439 # and @abadger to relicense from GPLv3+ def is_bind_mounted(module, linux_mounts, dest, src=None, fstype=None): """Return whether the dest is bind mounted :arg module: The AnsibleModule (used for helper functions) :arg dest: The directory to be mounted under. This is the primary means of identifying whether the destination is mounted. :kwarg src: The source directory. If specified, this is used to help ensure that we are detecting that the correct source is mounted there. :kwarg fstype: The filesystem type. If specified this is also used to help ensure that we are detecting the right mount. :kwarg linux_mounts: Cached list of mounts for Linux. :returns: True if the dest is mounted with src otherwise False. """ is_mounted = False if platform.system() == 'Linux' and linux_mounts is not None: if src is None: # That's for unmounted/absent if dest in linux_mounts: is_mounted = True else: if dest in linux_mounts: is_mounted = linux_mounts[dest]['src'] == src else: bin_path = module.get_bin_path('mount', required=True) cmd = '%s -l' % bin_path rc, out, err = module.run_command(cmd) mounts = [] if len(out): mounts = to_native(out).strip().split('\n') for mnt in mounts: arguments = mnt.split() if ( (arguments[0] == src or src is None) and arguments[2] == dest and (arguments[4] == fstype or fstype is None)): is_mounted = True if is_mounted: break return is_mounted def get_linux_mounts(module, mntinfo_file="/proc/self/mountinfo"): """Gather mount information""" try: f = open(mntinfo_file) except IOError: return lines = map(str.strip, f.readlines()) try: f.close() except IOError: module.fail_json(msg="Cannot close file %s" % mntinfo_file) mntinfo = {} for line in lines: fields = line.split() record = { 'id': int(fields[0]), 'parent_id': int(fields[1]), 'root': fields[3], 'dst': fields[4], 'opts': fields[5], 'fs': fields[-3], 'src': fields[-2] } mntinfo[record['id']] = record mounts = {} for mnt in mntinfo.values(): if mnt['parent_id'] != 1 and mnt['parent_id'] in mntinfo: m = mntinfo[mnt['parent_id']] if ( len(m['root']) > 1 and mnt['root'].startswith("%s/" % m['root'])): # Omit the parent's root in the child's root # == Example: # 140 136 253:2 /rootfs / rw - ext4 /dev/sdb2 rw # 141 140 253:2 /rootfs/tmp/aaa /tmp/bbb rw - ext4 /dev/sdb2 rw # == Expected result: # src=/tmp/aaa mnt['root'] = mnt['root'][len(m['root']):] # Prepend the parent's dst to the child's root # == Example: # 42 60 0:35 / /tmp rw - tmpfs tmpfs rw # 78 42 0:35 /aaa /tmp/bbb rw - tmpfs tmpfs rw # == Expected result: # src=/tmp/aaa if m['dst'] != '/': mnt['root'] = "%s%s" % (m['dst'], mnt['root']) src = mnt['root'] else: src = mnt['src'] record = { 'dst': mnt['dst'], 'src': src, 'opts': mnt['opts'], 'fs': mnt['fs'] } mounts[mnt['dst']] = record return mounts def _is_same_mount_src(module, src, mountpoint, linux_mounts): """Return True if the mounted fs on mountpoint is the same source than src. Return False if mountpoint is not a mountpoint""" # If the provided mountpoint is not a mountpoint, don't waste time if ( not ismount(mountpoint) and not is_bind_mounted(module, linux_mounts, mountpoint)): return False # Treat Linux bind mounts if platform.system() == 'Linux' and linux_mounts is not None: # For Linux bind mounts only: the mount command does not return # the actual source for bind mounts, but the device of the source. # is_bind_mounted() called with the 'src' parameter will return True if # the mountpoint is a bind mount AND the source FS is the same than 'src'. # is_bind_mounted() is not reliable on Solaris, NetBSD and OpenBSD. # But we can rely on 'mount -v' on all other platforms, and Linux non-bind mounts. if is_bind_mounted(module, linux_mounts, mountpoint, src): return True # mount with parameter -v has a close behavior on Linux, *BSD, SunOS # Requires -v with SunOS. Without -v, source and destination are reversed # Output format differs from a system to another, but field[0:3] are consistent: [src, 'on', dest] cmd = '%s -v' % module.get_bin_path('mount', required=True) rc, out, err = module.run_command(cmd) mounts = [] if len(out): mounts = to_native(out).strip().split('\n') else: module.fail_json(msg="Unable to retrieve mount info with command '%s'" % cmd) for mnt in mounts: fields = mnt.split() mp_src = fields[0] mp_dst = fields[2] if mp_src == src and mp_dst == mountpoint: return True return False def main(): module = AnsibleModule( argument_spec=dict( boot=dict(type='bool', default=True), dump=dict(type='str', default='0'), fstab=dict(type='str'), fstype=dict(type='str'), path=dict(type='path', required=True, aliases=['name']), opts=dict(type='str'), passno=dict(type='str', no_log=False, default='0'), 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']), ), supports_check_mode=True, required_if=( ['state', 'mounted', ['src', 'fstype']], ['state', 'present', ['src', 'fstype']], ['state', 'ephemeral', ['src', 'fstype']] ), ) # solaris args: # name, src, fstype, opts, boot, passno, state, fstab=/etc/vfstab # linux args: # name, src, fstype, opts, dump, passno, state, fstab=/etc/fstab # Note: Do not modify module.params['fstab'] as we need to know if the user # explicitly specified it in mount() and remount() if platform.system().lower() == 'sunos': args = dict( name=module.params['path'], opts='-', passno='-', fstab=module.params['fstab'], boot='yes' if module.params['boot'] else 'no', warnings=[] ) if args['fstab'] is None: args['fstab'] = '/etc/vfstab' else: args = dict( name=module.params['path'], opts='defaults', dump='0', passno='0', fstab=module.params['fstab'], boot='yes', warnings=[] ) if args['fstab'] is None: args['fstab'] = '/etc/fstab' # FreeBSD doesn't have any 'default' so set 'rw' instead if platform.system() == 'FreeBSD': args['opts'] = 'rw' args['backup_file'] = "" linux_mounts = [] # Cache all mounts here in order we have consistent results if we need to # call is_bind_mounted() multiple times if platform.system() == 'Linux': linux_mounts = get_linux_mounts(module) if linux_mounts is None: args['warnings'].append('Cannot open file /proc/self/mountinfo.' ' Bind mounts might be misinterpreted.') # Override defaults with user specified params for key in ('src', 'fstype', 'passno', 'opts', 'dump', 'fstab'): if module.params[key] is not None: args[key] = module.params[key] if platform.system().lower() == 'linux' or platform.system().lower().endswith('bsd'): # Linux, FreeBSD, NetBSD and OpenBSD have 'noauto' as mount option to # handle mount on boot. To avoid mount option conflicts, if 'noauto' # specified in 'opts', mount module will ignore 'boot'. opts = args['opts'].split(',') if 'noauto' in opts: args['warnings'].append("Ignore the 'boot' due to 'opts' contains 'noauto'.") elif not module.params['boot']: args['boot'] = 'no' if 'defaults' in opts: args['warnings'].append("Ignore the 'boot' due to 'opts' contains 'defaults'.") else: opts.append('noauto') args['opts'] = ','.join(opts) # If fstab file does not exist, we first need to create it. This mainly # happens when fstab option is passed to the module. # If state is 'ephemeral', we do not need fstab file if module.params['state'] != 'ephemeral': if not os.path.exists(args['fstab']): if not os.path.exists(os.path.dirname(args['fstab'])): os.makedirs(os.path.dirname(args['fstab'])) try: open(args['fstab'], 'a').close() except PermissionError as e: module.fail_json(msg="Failed to open %s due to permission issue" % args['fstab']) except Exception as e: module.fail_json(msg="Failed to open %s due to %s" % (args['fstab'], to_native(e))) # absent: # Remove from fstab and unmounted. # unmounted: # Do not change fstab state, but unmount. # present: # Add to fstab, do not change mount state. # mounted: # Add to fstab if not there and make sure it is mounted. If it has # changed in fstab then remount it. # ephemeral: # Do not change fstab state, but mount. state = module.params['state'] name = module.params['path'] changed = False if state == 'absent_from_fstab': name, changed = unset_mount(module, args) elif state == 'absent': name, changed = unset_mount(module, args) if changed and not module.check_mode: if ismount(name) or is_bind_mounted(module, linux_mounts, name): res, msg = umount(module, name) if res: module.fail_json( msg="Error unmounting %s: %s" % (name, msg)) if os.path.exists(name): try: os.rmdir(name) except (OSError, IOError) as e: module.fail_json(msg="Error rmdir %s: %s" % (name, to_native(e))) elif state == 'unmounted': if ismount(name) or is_bind_mounted(module, linux_mounts, name): if not module.check_mode: res, msg = umount(module, name) if res: module.fail_json( msg="Error unmounting %s: %s" % (name, msg)) changed = True elif state == 'mounted' or state == 'ephemeral': dirs_created = [] if not os.path.exists(name) and not module.check_mode: try: # Something like mkdir -p but with the possibility to undo. # Based on some copy-paste from the "file" module. curpath = '' for dirname in name.strip('/').split('/'): curpath = '/'.join([curpath, dirname]) # Remove leading slash if we're creating a relative path if not os.path.isabs(name): curpath = curpath.lstrip('/') b_curpath = to_bytes(curpath, errors='surrogate_or_strict') if not os.path.exists(b_curpath): try: os.mkdir(b_curpath) dirs_created.append(b_curpath) except OSError as ex: # Possibly something else created the dir since the os.path.exists # check above. As long as it's a dir, we don't need to error out. if not (ex.errno == errno.EEXIST and os.path.isdir(b_curpath)): raise except (OSError, IOError) as e: module.fail_json( msg="Error making dir %s: %s" % (name, to_native(e))) # ephemeral: completely ignore fstab if state != 'ephemeral': name, backup_lines, changed = _set_mount_save_old(module, args) else: name, backup_lines, changed = args['name'], [], False res = 0 if ( ismount(name) or is_bind_mounted( module, linux_mounts, name, args['src'], args['fstype'])): if changed and not module.check_mode: res, msg = remount(module, args) changed = True # When 'state' == 'ephemeral', we don't know what is in fstab, and 'changed' is always False if state == 'ephemeral': # If state == 'ephemeral', check if the mountpoint src == module.params['src'] # If it doesn't, fail to prevent unwanted unmount or unwanted mountpoint override if _is_same_mount_src(module, args['src'], args['name'], linux_mounts): changed = True if not module.check_mode: res, msg = remount(module, args) else: module.fail_json( msg=( 'Ephemeral mount point is already mounted with a different ' 'source than the specified one. Failing in order to prevent an ' 'unwanted unmount or override operation. Try replacing this command with ' 'a "state: unmounted" followed by a "state: ephemeral", or use ' 'a different destination path.')) else: # If not already mounted, mount it changed = True if not module.check_mode: res, msg = mount(module, args) if res: # Not restoring fstab after a failed mount was reported as a bug, # ansible/ansible#59183 # A non-working fstab entry may break the system at the reboot, # so undo all the changes if possible. try: if state != 'ephemeral': write_fstab(module, backup_lines, args['fstab']) except Exception: pass try: for dirname in dirs_created[::-1]: os.rmdir(dirname) except Exception: pass module.fail_json(msg="Error mounting %s: %s" % (name, msg)) elif state == 'present': name, changed = set_mount(module, args) elif state == 'remounted': if not module.check_mode: res, msg = remount(module, args) if res: module.fail_json(msg="Error remounting %s: %s" % (name, msg)) changed = True else: module.fail_json(msg='Unexpected position reached') # If the managed node is Solaris, convert the boot value type to Boolean # to match the type of return value with the module argument. if platform.system().lower() == 'sunos': args['boot'] = boolean(args['boot']) module.exit_json(changed=changed, **args) if __name__ == '__main__': main()