Add ephemeral state to mount fs without altering fstab

- Edit DOCUMENTATION section to add ephemeral state
- Edit EXAMPLES section to add ephemeral state example
- Add new function `_set_ephemeral_args` to replace `_set_fstab_args` when using ephemeral state
- Add new function `_is_same_mount_src` to determine if the mounted volume on the destination path has the same source than the one supplied to the module
- Add new function `_get_mount_info` to avoid redundant code between functions `get_linux_mounts` and `_is_same_mount_src`
- Modify `get_linux_mount` to use the new function `_get_mount_info`. Behavior is preserved.
- Integrate `ephemeral` parameter treatment into `mounted` treatment, and add `if` statements to avoid IO from/to fstab
- Add `ephemeral` as a possible value for the `state` parameter in `main()`
- Add `required_if` dependencies for `ephemeral` state
This commit is contained in:
NeodymiumFerBore 2021-09-12 22:41:55 +02:00 committed by GitHub
parent 610717ca76
commit 5c70124433
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -31,12 +31,12 @@ options:
src: src:
description: description:
- Device (or NFS volume, or something else) to be mounted on I(path). - Device (or NFS volume, or something else) to be mounted on I(path).
- Required when I(state) set to C(present) or C(mounted). - Required when I(state) set to C(present), C(mounted) or C(ephemeral).
type: path type: path
fstype: fstype:
description: description:
- Filesystem type. - Filesystem type.
- Required when I(state) is C(present) or C(mounted). - Required when I(state) is C(present), C(mounted) or C(ephemeral).
type: str type: str
opts: opts:
description: description:
@ -48,7 +48,7 @@ options:
- Note that if set to C(null) and I(state) set to C(present), - 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 it will cease to work and duplicate entries will be made
with subsequent runs. with subsequent runs.
- Has no effect on Solaris systems. - Has no effect on Solaris systems or when used with C(ephemeral).
type: str type: str
default: 0 default: 0
passno: passno:
@ -57,7 +57,7 @@ options:
- Note that if set to C(null) and I(state) set to C(present), - 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 it will cease to work and duplicate entries will be made
with subsequent runs. with subsequent runs.
- Deprecated on Solaris systems. - Deprecated on Solaris systems. Has no effect when used with C(ephemeral).
type: str type: str
default: 0 default: 0
state: state:
@ -68,6 +68,13 @@ options:
- If C(unmounted), the device will be unmounted without changing I(fstab). - If C(unmounted), the device will be unmounted without changing I(fstab).
- C(present) only specifies that the device is to be configured in - C(present) only specifies that the device is to be configured in
I(fstab) and does not trigger or require a mount. 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 C(path)
has already a device mounted on, and its I(src) is different than C(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 value of C(fstab) is ignored.
- C(absent) specifies that the device mount's entry will be removed from - 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 I(fstab) and will also unmount the device and remove the mount
point. point.
@ -80,7 +87,7 @@ options:
instead to work around this issue. instead to work around this issue.
type: str type: str
required: true required: true
choices: [ absent, mounted, present, unmounted, remounted ] choices: [ absent, mounted, present, unmounted, remounted, ephemeral ]
fstab: fstab:
description: description:
- File to use instead of C(/etc/fstab). - File to use instead of C(/etc/fstab).
@ -89,6 +96,7 @@ options:
- OpenBSD does not allow specifying alternate fstab files with mount so do not - 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. 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 defaults to /etc/fstab or /etc/vfstab on Solaris.
- This parameter is ignored when I(state) is set to C(ephemeral).
type: str type: str
boot: boot:
description: description:
@ -100,6 +108,7 @@ options:
to mount options in I(/etc/fstab). to mount options in I(/etc/fstab).
- To avoid mount option conflicts, if C(noauto) specified in C(opts), - To avoid mount option conflicts, if C(noauto) specified in C(opts),
mount module will ignore C(boot). mount module will ignore C(boot).
- This parameter is ignored when I(state) is set to C(ephemeral).
type: bool type: bool
default: yes default: yes
backup: backup:
@ -184,6 +193,14 @@ EXAMPLES = r'''
boot: no boot: no
state: mounted state: mounted
fstype: nfs 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 errno
@ -426,6 +443,23 @@ def _set_fstab_args(fstab_file):
return result 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): def mount(module, args):
"""Mount up a path or remount if needed.""" """Mount up a path or remount if needed."""
@ -442,7 +476,10 @@ def mount(module, args):
'OpenBSD does not support alternate fstab files. Do not ' 'OpenBSD does not support alternate fstab files. Do not '
'specify the fstab parameter for OpenBSD hosts')) 'specify the fstab parameter for OpenBSD hosts'))
else: else:
cmd += _set_fstab_args(args['fstab']) if module.params['state'] != 'ephemeral':
cmd += _set_fstab_args(args['fstab'])
else:
cmd += _set_ephemeral_args(args)
cmd += [name] cmd += [name]
@ -494,7 +531,10 @@ def remount(module, args):
'OpenBSD does not support alternate fstab files. Do not ' 'OpenBSD does not support alternate fstab files. Do not '
'specify the fstab parameter for OpenBSD hosts')) 'specify the fstab parameter for OpenBSD hosts'))
else: else:
cmd += _set_fstab_args(args['fstab']) if module.params['state'] != 'ephemeral':
cmd += _set_fstab_args(args['fstab'])
else:
cmd += _set_ephemeral_args(args)
cmd += [args['name']] cmd += [args['name']]
out = err = '' out = err = ''
@ -587,9 +627,8 @@ def is_bind_mounted(module, linux_mounts, dest, src=None, fstype=None):
return is_mounted return is_mounted
def get_linux_mounts(module, mntinfo_file="/proc/self/mountinfo"): def _get_mount_info(module, mntinfo_file="/proc/self/mountinfo"):
"""Gather mount information""" """Return raw mount information"""
try: try:
f = open(mntinfo_file) f = open(mntinfo_file)
except IOError: except IOError:
@ -602,6 +641,17 @@ def get_linux_mounts(module, mntinfo_file="/proc/self/mountinfo"):
except IOError: except IOError:
module.fail_json(msg="Cannot close file %s" % mntinfo_file) module.fail_json(msg="Cannot close file %s" % mntinfo_file)
return lines
def get_linux_mounts(module, mntinfo_file="/proc/self/mountinfo"):
"""Gather mount information"""
lines = _get_mount_info(module)
# Keep same behavior than before
if lines is None:
return
mntinfo = {} mntinfo = {}
for line in lines: for line in lines:
@ -659,6 +709,24 @@ def get_linux_mounts(module, mntinfo_file="/proc/self/mountinfo"):
return mounts return mounts
def _is_same_mount_src(module, args, mntinfo_file="/proc/self/mountinfo"):
"""Return True if the mounted fs on mountpoint is the same source than src"""
mountpoint = args['name']
src = args['src']
lines = _get_mount_info(module)
# If this function is used and we cannot retrieve mount info, we must fail to avoid unexpected behavior
if lines is None:
module.fail_json(msg="Unable to retrieve mount info from '%s'" % mntinfo_file)
for line in lines:
fields = line.split()
if fields[4] == mountpoint and fields[-2] == src:
return True
# (dst == mountpoint and src == name) was never reached
return False
def main(): def main():
module = AnsibleModule( module = AnsibleModule(
argument_spec=dict( argument_spec=dict(
@ -671,12 +739,13 @@ def main():
passno=dict(type='str', no_log=False), passno=dict(type='str', no_log=False),
src=dict(type='path'), src=dict(type='path'),
backup=dict(type='bool', default=False), backup=dict(type='bool', default=False),
state=dict(type='str', required=True, choices=['absent', 'mounted', 'present', 'unmounted', 'remounted']), state=dict(type='str', required=True, choices=['absent', 'mounted', 'present', 'unmounted', 'remounted', 'ephemeral']),
), ),
supports_check_mode=True, supports_check_mode=True,
required_if=( required_if=(
['state', 'mounted', ['src', 'fstype']], ['state', 'mounted', ['src', 'fstype']],
['state', 'present', ['src', 'fstype']], ['state', 'present', ['src', 'fstype']],
['state', 'ephemeral', ['src', 'fstype']]
), ),
) )
@ -747,15 +816,17 @@ def main():
# If fstab file does not exist, we first need to create it. This mainly # If fstab file does not exist, we first need to create it. This mainly
# happens when fstab option is passed to the module. # happens when fstab option is passed to the module.
if not os.path.exists(args['fstab']): # If state is 'ephemeral', we do not need fstab file
if not os.path.exists(os.path.dirname(args['fstab'])): if module.params['state'] != 'ephemeral':
os.makedirs(os.path.dirname(args['fstab'])) if not os.path.exists(args['fstab']):
try: if not os.path.exists(os.path.dirname(args['fstab'])):
open(args['fstab'], 'a').close() os.makedirs(os.path.dirname(args['fstab']))
except PermissionError as e: try:
module.fail_json(msg="Failed to open %s due to permission issue" % args['fstab']) open(args['fstab'], 'a').close()
except Exception as e: except PermissionError as e:
module.fail_json(msg="Failed to open %s due to %s" % (args['fstab'], to_native(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: # absent:
# Remove from fstab and unmounted. # Remove from fstab and unmounted.
@ -766,6 +837,8 @@ def main():
# mounted: # mounted:
# Add to fstab if not there and make sure it is mounted. If it has # Add to fstab if not there and make sure it is mounted. If it has
# changed in fstab then remount it. # changed in fstab then remount it.
# ephemeral:
# Do not change fstab state, but mount.
state = module.params['state'] state = module.params['state']
name = module.params['path'] name = module.params['path']
@ -797,7 +870,7 @@ def main():
msg="Error unmounting %s: %s" % (name, msg)) msg="Error unmounting %s: %s" % (name, msg))
changed = True changed = True
elif state == 'mounted': elif state == 'mounted' or state == 'ephemeral':
dirs_created = [] dirs_created = []
if not os.path.exists(name) and not module.check_mode: if not os.path.exists(name) and not module.check_mode:
try: try:
@ -825,7 +898,11 @@ def main():
module.fail_json( module.fail_json(
msg="Error making dir %s: %s" % (name, to_native(e))) msg="Error making dir %s: %s" % (name, to_native(e)))
name, backup_lines, changed = _set_mount_save_old(module, args) # 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 res = 0
if ( if (
@ -835,7 +912,25 @@ def main():
if changed and not module.check_mode: if changed and not module.check_mode:
res, msg = remount(module, args) res, msg = remount(module, args)
changed = True 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):
res, msg = remount(module, args)
changed = True
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: else:
# If not already mounted, mount it
changed = True changed = True
if not module.check_mode: if not module.check_mode:
@ -847,7 +942,8 @@ def main():
# A non-working fstab entry may break the system at the reboot, # A non-working fstab entry may break the system at the reboot,
# so undo all the changes if possible. # so undo all the changes if possible.
try: try:
write_fstab(module, backup_lines, args['fstab']) if state != 'ephemeral':
write_fstab(module, backup_lines, args['fstab'])
except Exception: except Exception:
pass pass