1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2026-02-04 16:01:55 +00:00
community.general/plugins/modules/filesystem.py
Felix Fontein dce8b507fd
[stable-11] filesystem: xfs resize: minimal required increment (#11033) (#11042)
filesystem: xfs resize: minimal required increment (#11033)

Internally XFS uses allocation groups. Allocation groups have a maximum
size of 1 TiB - 1 block. For devices >= 4 TiB XFS uses max size
allocation groups. If a filesystem is extended and the last allocation
group is already at max size, a new allocation group is added. An
allocation group seems to require at least 64 4 KiB blocks.

For devices with integer TiB size (>4), this creates a filesystem that
has initially has 1 unused block per TiB size. The `resize` option
detects this unused space, and tries to resize the filesystem.  The
xfs_growfs call is successful (exit 0), but does not increase the file
system size. This is detected as repeated change in the task.

Test case:
```
- hosts: localhost
  tasks:
    - ansible.builtin.command:
        cmd: truncate -s 4T /media/xfs.img
        creates: /media/xfs.img
      notify: loopdev xfs

    - ansible.builtin.meta: flush_handlers

    - name: pickup xfs.img resize
      ansible.builtin.command:
        cmd: losetup -c /dev/loop0
      changed_when: false

    - community.general.filesystem:
        dev: "/dev/loop0"
        fstype: "xfs"

    - ansible.posix.mount:
        src: "/dev/loop0"
        fstype: "xfs"
        path: "/media/xfs"
        state: "mounted"

    # always shows a diff even for newly created filesystems
    - community.general.filesystem:
        dev: "/dev/loop0"
        fstype: "xfs"
        resizefs: true

  handlers:
    - name: loopdev xfs
      ansible.builtin.command:
        cmd: losetup /dev/loop0 /media/xfs.img
```

NB: If the last allocation group is not yet at max size, the filesystem
can be resized. Detecting this requires considering the XFS topology.
Other filesystems (at least ext4) also seem to require a minimum
increment after the initial device size, but seem to use the entire
device after initial creation.

Fun observation: creating a 64(+) TiB filesystem leaves a 64(+) block
gap at the end, that is allocated in a subsequent xfs_growfs call.


(cherry picked from commit f5943201b9)

Co-authored-by: jnaab <25617714+jnaab@users.noreply.github.com>
Co-authored-by: Johannes Naab <johannes.naab@hetzner-cloud.de>
2025-11-08 10:00:56 +01:00

734 lines
26 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2021, quidame <quidame@poivron.org>
# Copyright (c) 2013, Alexander Bulimov <lazywolf0@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
author:
- Alexander Bulimov (@abulimov)
- quidame (@quidame)
module: filesystem
short_description: Makes a filesystem
description:
- This module creates a filesystem.
extends_documentation_fragment:
- community.general.attributes
attributes:
check_mode:
support: full
diff_mode:
support: none
options:
state:
description:
- If O(state=present), the filesystem is created if it does not already exist, that is the default behaviour if O(state)
is omitted.
- If O(state=absent), filesystem signatures on O(dev) are wiped if it contains a filesystem (as known by C(blkid)).
- When O(state=absent), all other options but O(dev) are ignored, and the module does not fail if the device O(dev)
does not actually exist.
type: str
choices: [present, absent]
default: present
version_added: 1.3.0
fstype:
choices: [bcachefs, btrfs, ext2, ext3, ext4, ext4dev, f2fs, lvm, ocfs2, reiserfs, xfs, vfat, swap, ufs]
description:
- Filesystem type to be created. This option is required with O(state=present) (or if O(state) is omitted).
- V(ufs) support has been added in community.general 3.4.0.
- V(bcachefs) support has been added in community.general 8.6.0.
type: str
aliases: [type]
dev:
description:
- Target path to block device (Linux) or character device (FreeBSD) or regular file (both).
- When setting Linux-specific filesystem types on FreeBSD, this module only works when applying to regular files, also known as
disk images.
- Currently V(lvm) (Linux-only) and V(ufs) (FreeBSD-only) do not support a regular file as their target O(dev).
- Support for character devices on FreeBSD has been added in community.general 3.4.0.
type: path
required: true
aliases: [device]
force:
description:
- If V(true), allows to create new filesystem on devices that already has filesystem.
type: bool
default: false
resizefs:
description:
- If V(true), if the block device and filesystem size differ, grow the filesystem into the space.
- >-
Supported when O(fstype) is one of: V(bcachefs), V(btrfs), V(ext2), V(ext3), V(ext4), V(ext4dev), V(f2fs), V(lvm), V(xfs), V(ufs) and V(vfat).
Attempts to resize other filesystem types fail.
- XFS only grows if mounted. Currently, the module is based on commands from C(util-linux) package to perform operations,
so resizing of XFS is not supported on FreeBSD systems.
- VFAT is likely to fail if C(fatresize < 1.04).
- Mutually exclusive with O(uuid).
type: bool
default: false
opts:
description:
- List of options to be passed to C(mkfs) command.
type: str
uuid:
description:
- Set filesystem's UUID to the given value.
- The UUID options specified in O(opts) take precedence over this value.
- See xfs_admin(8) (C(xfs)), tune2fs(8) (C(ext2), C(ext3), C(ext4), C(ext4dev)) for possible values.
- For O(fstype=lvm) the value is ignored, it resets the PV UUID if set.
- Supported for O(fstype) being one of V(bcachefs), V(ext2), V(ext3), V(ext4), V(ext4dev), V(lvm), or V(xfs).
- This is B(not idempotent). Specifying this option always results in a change.
- Mutually exclusive with O(resizefs).
type: str
version_added: 7.1.0
requirements:
- Uses specific tools related to the O(fstype) for creating or resizing a filesystem (from packages e2fsprogs, xfsprogs,
dosfstools, and so on).
- Uses generic tools mostly related to the Operating System (Linux or FreeBSD) or available on both, as C(blkid).
- On FreeBSD, either C(util-linux) or C(e2fsprogs) package is required.
notes:
- Potential filesystems on O(dev) are checked using C(blkid). In case C(blkid) is unable to detect a filesystem (and in
case C(fstyp) on FreeBSD is also unable to detect a filesystem), this filesystem is overwritten even if O(force=false).
- On FreeBSD systems, both C(e2fsprogs) and C(util-linux) packages provide a C(blkid) command that is compatible with this
module. However, these packages conflict with each other, and only the C(util-linux) package provides the command required
to not fail when O(state=absent).
seealso:
- module: community.general.filesize
- module: ansible.posix.mount
- name: xfs_admin(8) manpage for Linux
description: Manual page of the GNU/Linux's xfs_admin implementation.
link: https://man7.org/linux/man-pages/man8/xfs_admin.8.html
- name: tune2fs(8) manpage for Linux
description: Manual page of the GNU/Linux's tune2fs implementation.
link: https://man7.org/linux/man-pages/man8/tune2fs.8.html
"""
EXAMPLES = r"""
- name: Create a ext2 filesystem on /dev/sdb1
community.general.filesystem:
fstype: ext2
dev: /dev/sdb1
- name: Create a ext4 filesystem on /dev/sdb1 and check disk blocks
community.general.filesystem:
fstype: ext4
dev: /dev/sdb1
opts: -cc
- name: Blank filesystem signature on /dev/sdb1
community.general.filesystem:
dev: /dev/sdb1
state: absent
- name: Create a filesystem on top of a regular file
community.general.filesystem:
dev: /path/to/disk.img
fstype: vfat
- name: Reset an xfs filesystem UUID on /dev/sdb1
community.general.filesystem:
fstype: xfs
dev: /dev/sdb1
uuid: generate
- name: Reset an ext4 filesystem UUID on /dev/sdb1
community.general.filesystem:
fstype: ext4
dev: /dev/sdb1
uuid: random
- name: Reset an LVM filesystem (PV) UUID on /dev/sdc
community.general.filesystem:
fstype: lvm
dev: /dev/sdc
uuid: random
"""
import os
import platform
import re
import stat
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.text.converters import to_native
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
class Device(object):
def __init__(self, module, path):
self.module = module
self.path = path
def size(self):
""" Return size in bytes of device. Returns int """
statinfo = os.stat(self.path)
if stat.S_ISBLK(statinfo.st_mode):
blockdev_cmd = self.module.get_bin_path("blockdev", required=True)
dummy, out, dummy = self.module.run_command([blockdev_cmd, "--getsize64", self.path], check_rc=True)
devsize_in_bytes = int(out)
elif stat.S_ISCHR(statinfo.st_mode) and platform.system() == 'FreeBSD':
diskinfo_cmd = self.module.get_bin_path("diskinfo", required=True)
dummy, out, dummy = self.module.run_command([diskinfo_cmd, self.path], check_rc=True)
devsize_in_bytes = int(out.split()[2])
elif os.path.isfile(self.path):
devsize_in_bytes = os.path.getsize(self.path)
else:
self.module.fail_json(changed=False, msg="Target device not supported: %s" % self)
return devsize_in_bytes
def get_mountpoint(self):
"""Return (first) mountpoint of device. Returns None when not mounted."""
cmd_findmnt = self.module.get_bin_path("findmnt", required=True)
# find mountpoint
rc, mountpoint, dummy = self.module.run_command([cmd_findmnt, "--mtab", "--noheadings", "--output",
"TARGET", "--source", self.path], check_rc=False)
if rc != 0:
mountpoint = None
else:
mountpoint = mountpoint.split('\n')[0]
return mountpoint
def __str__(self):
return self.path
class Filesystem(object):
MKFS = None
MKFS_FORCE_FLAGS = []
MKFS_SET_UUID_OPTIONS = None
MKFS_SET_UUID_EXTRA_OPTIONS = []
INFO = None
GROW = None
GROW_SLACK = 0
GROW_MAX_SPACE_FLAGS = []
GROW_MOUNTPOINT_ONLY = False
CHANGE_UUID = None
CHANGE_UUID_OPTION = None
CHANGE_UUID_OPTION_HAS_ARG = True
LANG_ENV = {'LANG': 'C', 'LC_ALL': 'C', 'LC_MESSAGES': 'C'}
def __init__(self, module):
self.module = module
@property
def fstype(self):
return type(self).__name__
def get_fs_size(self, dev):
"""Return size in bytes of filesystem on device (integer).
Should query the info with a per-fstype command that can access the
device whenever it is mounted or not, and parse the command output.
Parser must ensure to return an integer, or raise a ValueError.
"""
raise NotImplementedError()
def create(self, opts, dev, uuid=None):
if self.module.check_mode:
return
if uuid and self.MKFS_SET_UUID_OPTIONS:
if not (set(self.MKFS_SET_UUID_OPTIONS) & set(opts)):
opts += [self.MKFS_SET_UUID_OPTIONS[0], uuid] + self.MKFS_SET_UUID_EXTRA_OPTIONS
mkfs = self.module.get_bin_path(self.MKFS, required=True)
cmd = [mkfs] + self.MKFS_FORCE_FLAGS + opts + [str(dev)]
self.module.run_command(cmd, check_rc=True)
if uuid and self.CHANGE_UUID and self.MKFS_SET_UUID_OPTIONS is None:
self.change_uuid(new_uuid=uuid, dev=dev)
def wipefs(self, dev):
if self.module.check_mode:
return
# wipefs comes with util-linux package (as 'blockdev' & 'findmnt' above)
# that is ported to FreeBSD. The use of dd as a portable fallback is
# not doable here if it needs get_mountpoint() (to prevent corruption of
# a mounted filesystem), since 'findmnt' is not available on FreeBSD,
# even in util-linux port for this OS.
wipefs = self.module.get_bin_path('wipefs', required=True)
cmd = [wipefs, "--all", str(dev)]
self.module.run_command(cmd, check_rc=True)
def grow_cmd(self, target):
"""Build and return the resizefs commandline as list."""
cmdline = [self.module.get_bin_path(self.GROW, required=True)]
cmdline += self.GROW_MAX_SPACE_FLAGS + [target]
return cmdline
def grow(self, dev):
"""Get dev and fs size and compare. Returns stdout of used command."""
devsize_in_bytes = dev.size()
try:
fssize_in_bytes = self.get_fs_size(dev)
except NotImplementedError:
self.module.fail_json(msg="module does not support resizing %s filesystem yet" % self.fstype)
except ValueError as err:
self.module.warn("unable to process %s output '%s'" % (self.INFO, to_native(err)))
self.module.fail_json(msg="unable to process %s output for %s" % (self.INFO, dev))
if fssize_in_bytes + self.GROW_SLACK >= devsize_in_bytes:
self.module.exit_json(changed=False, msg="%s filesystem is using the whole device %s" % (self.fstype, dev))
elif self.module.check_mode:
self.module.exit_json(changed=True, msg="resizing filesystem %s on device %s" % (self.fstype, dev))
if self.GROW_MOUNTPOINT_ONLY:
mountpoint = dev.get_mountpoint()
if not mountpoint:
self.module.fail_json(msg="%s needs to be mounted for %s operations" % (dev, self.fstype))
grow_target = mountpoint
else:
grow_target = str(dev)
dummy, out, dummy = self.module.run_command(self.grow_cmd(grow_target), check_rc=True)
return out
def change_uuid_cmd(self, new_uuid, target):
"""Build and return the UUID change command line as list."""
cmdline = [self.module.get_bin_path(self.CHANGE_UUID, required=True)]
if self.CHANGE_UUID_OPTION_HAS_ARG:
cmdline += [self.CHANGE_UUID_OPTION, new_uuid, target]
else:
cmdline += [self.CHANGE_UUID_OPTION, target]
return cmdline
def change_uuid(self, new_uuid, dev):
"""Change filesystem UUID. Returns stdout of used command"""
if self.module.check_mode:
self.module.exit_json(change=True, msg='Changing %s filesystem UUID on device %s' % (self.fstype, dev))
dummy, out, dummy = self.module.run_command(self.change_uuid_cmd(new_uuid=new_uuid, target=str(dev)), check_rc=True)
return out
class Ext(Filesystem):
MKFS_FORCE_FLAGS = ['-F']
MKFS_SET_UUID_OPTIONS = ['-U']
INFO = 'tune2fs'
GROW = 'resize2fs'
CHANGE_UUID = 'tune2fs'
CHANGE_UUID_OPTION = "-U"
def get_fs_size(self, dev):
"""Get Block count and Block size and return their product."""
cmd = self.module.get_bin_path(self.INFO, required=True)
dummy, out, dummy = self.module.run_command([cmd, '-l', str(dev)], check_rc=True, environ_update=self.LANG_ENV)
block_count = block_size = None
for line in out.splitlines():
if 'Block count:' in line:
block_count = int(line.split(':')[1].strip())
elif 'Block size:' in line:
block_size = int(line.split(':')[1].strip())
if None not in (block_size, block_count):
break
else:
raise ValueError(repr(out))
return block_size * block_count
class Ext2(Ext):
MKFS = 'mkfs.ext2'
class Ext3(Ext):
MKFS = 'mkfs.ext3'
class Ext4(Ext):
MKFS = 'mkfs.ext4'
class XFS(Filesystem):
MKFS = 'mkfs.xfs'
MKFS_FORCE_FLAGS = ['-f']
INFO = 'xfs_info'
GROW = 'xfs_growfs'
# XFS (defaults with 4KiB blocksize) requires at least 64 block of free
# space to add a new allocation group, avoid resizing (noop, but shown as
# diff) if the difference between the filesystem and the device is less
GROW_SLACK = 64 * 4096 - 1
GROW_MOUNTPOINT_ONLY = True
CHANGE_UUID = "xfs_admin"
CHANGE_UUID_OPTION = "-U"
def get_fs_size(self, dev):
"""Get bsize and blocks and return their product."""
cmdline = [self.module.get_bin_path(self.INFO, required=True)]
# Depending on the versions, xfs_info is able to get info from the
# device, whenever it is mounted or not, or only if unmounted, or
# only if mounted, or not at all. For any version until now, it is
# able to query info from the mountpoint. So try it first, and use
# device as the last resort: it may or may not work.
mountpoint = dev.get_mountpoint()
if mountpoint:
cmdline += [mountpoint]
else:
cmdline += [str(dev)]
dummy, out, dummy = self.module.run_command(cmdline, check_rc=True, environ_update=self.LANG_ENV)
block_size = block_count = None
for line in out.splitlines():
col = line.split('=')
if col[0].strip() == 'data':
if col[1].strip() == 'bsize':
block_size = int(col[2].split()[0])
if col[2].split()[1] == 'blocks':
block_count = int(col[3].split(',')[0])
if None not in (block_size, block_count):
break
else:
raise ValueError(repr(out))
return block_size * block_count
class Reiserfs(Filesystem):
MKFS = 'mkfs.reiserfs'
MKFS_FORCE_FLAGS = ['-q']
class Bcachefs(Filesystem):
MKFS = 'mkfs.bcachefs'
MKFS_FORCE_FLAGS = ['--force']
MKFS_SET_UUID_OPTIONS = ['-U', '--uuid']
INFO = 'bcachefs'
GROW = 'bcachefs'
GROW_MAX_SPACE_FLAGS = ['device', 'resize']
def get_fs_size(self, dev):
"""Return size in bytes of filesystem on device (integer)."""
dummy, stdout, dummy = self.module.run_command([self.module.get_bin_path(self.INFO),
'show-super', str(dev)], check_rc=True)
for line in stdout.splitlines():
if "Size: " in line:
parts = line.split()
unit = parts[2]
base = None
exp = None
units_2 = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]
units_10 = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
try:
exp = units_2.index(unit)
base = 1024
except ValueError:
exp = units_10.index(unit)
base = 1000
if exp == 0:
value = int(parts[1])
else:
value = float(parts[1])
if base is not None and exp is not None:
return int(value * pow(base, exp))
raise ValueError(repr(stdout))
class Btrfs(Filesystem):
MKFS = 'mkfs.btrfs'
INFO = 'btrfs'
GROW = 'btrfs'
GROW_MAX_SPACE_FLAGS = ['filesystem', 'resize', 'max']
GROW_MOUNTPOINT_ONLY = True
def __init__(self, module):
super(Btrfs, self).__init__(module)
mkfs = self.module.get_bin_path(self.MKFS, required=True)
dummy, stdout, stderr = self.module.run_command([mkfs, '--version'], check_rc=True)
match = re.search(r" v([0-9.]+)", stdout)
if not match:
# v0.20-rc1 use stderr
match = re.search(r" v([0-9.]+)", stderr)
if match:
# v0.20-rc1 doesn't have --force parameter added in following version v3.12
if LooseVersion(match.group(1)) >= LooseVersion('3.12'):
self.MKFS_FORCE_FLAGS = ['-f']
else:
# assume version is greater or equal to 3.12
self.MKFS_FORCE_FLAGS = ['-f']
self.module.warn('Unable to identify mkfs.btrfs version (%r, %r)' % (stdout, stderr))
def get_fs_size(self, dev):
"""Return size in bytes of filesystem on device (integer)."""
mountpoint = dev.get_mountpoint()
if not mountpoint:
self.module.fail_json(msg="%s needs to be mounted for %s operations" % (dev, self.fstype))
dummy, stdout, dummy = self.module.run_command([self.module.get_bin_path(self.INFO),
'filesystem', 'usage', '-b', mountpoint], check_rc=True)
for line in stdout.splitlines():
if "Device size" in line:
return int(line.split()[-1])
raise ValueError(repr(stdout))
class Ocfs2(Filesystem):
MKFS = 'mkfs.ocfs2'
MKFS_FORCE_FLAGS = ['-Fx']
class F2fs(Filesystem):
MKFS = 'mkfs.f2fs'
INFO = 'dump.f2fs'
GROW = 'resize.f2fs'
def __init__(self, module):
super(F2fs, self).__init__(module)
mkfs = self.module.get_bin_path(self.MKFS, required=True)
dummy, out, dummy = self.module.run_command([mkfs, os.devnull], check_rc=False, environ_update=self.LANG_ENV)
# Looking for " F2FS-tools: mkfs.f2fs Ver: 1.10.0 (2018-01-30)"
# mkfs.f2fs displays version since v1.2.0
match = re.search(r"F2FS-tools: mkfs.f2fs Ver: ([0-9.]+) \(", out)
if match is not None:
# Since 1.9.0, mkfs.f2fs check overwrite before make filesystem
# before that version -f switch wasn't used
if LooseVersion(match.group(1)) >= LooseVersion('1.9.0'):
self.MKFS_FORCE_FLAGS = ['-f']
def get_fs_size(self, dev):
"""Get sector size and total FS sectors and return their product."""
cmd = self.module.get_bin_path(self.INFO, required=True)
dummy, out, dummy = self.module.run_command([cmd, str(dev)], check_rc=True, environ_update=self.LANG_ENV)
sector_size = sector_count = None
for line in out.splitlines():
if 'Info: sector size = ' in line:
# expected: 'Info: sector size = 512'
sector_size = int(line.split()[4])
elif 'Info: total FS sectors = ' in line:
# expected: 'Info: total FS sectors = 102400 (50 MB)'
sector_count = int(line.split()[5])
if None not in (sector_size, sector_count):
break
else:
raise ValueError(repr(out))
return sector_size * sector_count
class VFAT(Filesystem):
INFO = 'fatresize'
GROW = 'fatresize'
GROW_MAX_SPACE_FLAGS = ['-s', 'max']
def __init__(self, module):
super(VFAT, self).__init__(module)
if platform.system() == 'FreeBSD':
self.MKFS = 'newfs_msdos'
else:
self.MKFS = 'mkfs.vfat'
def get_fs_size(self, dev):
"""Get and return size of filesystem, in bytes."""
cmd = self.module.get_bin_path(self.INFO, required=True)
dummy, out, dummy = self.module.run_command([cmd, '--info', str(dev)], check_rc=True, environ_update=self.LANG_ENV)
fssize = None
for line in out.splitlines()[1:]:
parts = line.split(':', 1)
if len(parts) < 2:
continue
param, value = parts
if param.strip() in ('Size', 'Cur size'):
fssize = int(value.strip())
break
else:
raise ValueError(repr(out))
return fssize
class LVM(Filesystem):
MKFS = 'pvcreate'
MKFS_FORCE_FLAGS = ['-f']
MKFS_SET_UUID_OPTIONS = ['-u', '--uuid']
MKFS_SET_UUID_EXTRA_OPTIONS = ['--norestorefile']
INFO = 'pvs'
GROW = 'pvresize'
CHANGE_UUID = 'pvchange'
CHANGE_UUID_OPTION = '-u'
CHANGE_UUID_OPTION_HAS_ARG = False
def get_fs_size(self, dev):
"""Get and return PV size, in bytes."""
cmd = self.module.get_bin_path(self.INFO, required=True)
dummy, size, dummy = self.module.run_command([cmd, '--noheadings', '-o', 'pv_size', '--units', 'b', '--nosuffix', str(dev)], check_rc=True)
pv_size = int(size)
return pv_size
class Swap(Filesystem):
MKFS = 'mkswap'
MKFS_FORCE_FLAGS = ['-f']
class UFS(Filesystem):
MKFS = 'newfs'
INFO = 'dumpfs'
GROW = 'growfs'
GROW_MAX_SPACE_FLAGS = ['-y']
def get_fs_size(self, dev):
"""Get providersize and fragment size and return their product."""
cmd = self.module.get_bin_path(self.INFO, required=True)
dummy, out, dummy = self.module.run_command([cmd, str(dev)], check_rc=True, environ_update=self.LANG_ENV)
fragmentsize = providersize = None
for line in out.splitlines():
if line.startswith('fsize'):
fragmentsize = int(line.split()[1])
elif 'providersize' in line:
providersize = int(line.split()[-1])
if None not in (fragmentsize, providersize):
break
else:
raise ValueError(repr(out))
return fragmentsize * providersize
FILESYSTEMS = {
'bcachefs': Bcachefs,
'ext2': Ext2,
'ext3': Ext3,
'ext4': Ext4,
'ext4dev': Ext4,
'f2fs': F2fs,
'reiserfs': Reiserfs,
'xfs': XFS,
'btrfs': Btrfs,
'vfat': VFAT,
'ocfs2': Ocfs2,
'LVM2_member': LVM,
'swap': Swap,
'ufs': UFS,
}
def main():
friendly_names = {
'lvm': 'LVM2_member',
}
fstypes = set(FILESYSTEMS.keys()) - set(friendly_names.values()) | set(friendly_names.keys())
# There is no "single command" to manipulate filesystems, so we map them all out and their options
module = AnsibleModule(
argument_spec=dict(
state=dict(type='str', default='present', choices=['present', 'absent']),
fstype=dict(type='str', aliases=['type'], choices=list(fstypes)),
dev=dict(type='path', required=True, aliases=['device']),
opts=dict(type='str'),
force=dict(type='bool', default=False),
resizefs=dict(type='bool', default=False),
uuid=dict(type='str'),
),
required_if=[
('state', 'present', ['fstype'])
],
mutually_exclusive=[
('resizefs', 'uuid'),
],
supports_check_mode=True,
)
state = module.params['state']
dev = module.params['dev']
fstype = module.params['fstype']
opts = module.params['opts']
force = module.params['force']
resizefs = module.params['resizefs']
uuid = module.params['uuid']
mkfs_opts = []
if opts is not None:
mkfs_opts = opts.split()
changed = False
if not os.path.exists(dev):
msg = "Device %s not found." % dev
if state == "present":
module.fail_json(msg=msg)
else:
module.exit_json(msg=msg)
dev = Device(module, dev)
# In case blkid/fstyp isn't able to identify an existing filesystem, device
# is considered as empty, then this existing filesystem would be overwritten
# even if force isn't enabled.
cmd = module.get_bin_path('blkid', required=True)
rc, raw_fs, err = module.run_command([cmd, '-c', os.devnull, '-o', 'value', '-s', 'TYPE', str(dev)])
fs = raw_fs.strip()
if not fs and platform.system() == 'FreeBSD':
cmd = module.get_bin_path('fstyp', required=True)
rc, raw_fs, err = module.run_command([cmd, str(dev)])
fs = raw_fs.strip()
if state == "present":
if fstype in friendly_names:
fstype = friendly_names[fstype]
try:
klass = FILESYSTEMS[fstype]
except KeyError:
module.fail_json(changed=False, msg="module does not support this filesystem (%s) yet." % fstype)
filesystem = klass(module)
if uuid and not (filesystem.CHANGE_UUID or filesystem.MKFS_SET_UUID_OPTIONS):
module.fail_json(changed=False, msg="module does not support UUID option for this filesystem (%s) yet." % fstype)
same_fs = fs and FILESYSTEMS.get(fs) == FILESYSTEMS[fstype]
if same_fs and not resizefs and not uuid and not force:
module.exit_json(changed=False)
elif same_fs:
if resizefs:
if not filesystem.GROW:
module.fail_json(changed=False, msg="module does not support resizing %s filesystem yet." % fstype)
out = filesystem.grow(dev)
module.exit_json(changed=True, msg=out)
elif uuid:
out = filesystem.change_uuid(new_uuid=uuid, dev=dev)
module.exit_json(changed=True, msg=out)
elif fs and not force:
module.fail_json(msg="'%s' is already used as %s, use force=true to overwrite" % (dev, fs), rc=rc, err=err)
# create fs
filesystem.create(opts=mkfs_opts, dev=dev, uuid=uuid)
changed = True
elif fs:
# wipe fs signatures
filesystem = Filesystem(module)
filesystem.wipefs(dev)
changed = True
module.exit_json(changed=changed)
if __name__ == '__main__':
main()