From f30a08d0492b40b0ac54d90a731e068ec5b4d4d1 Mon Sep 17 00:00:00 2001 From: Kai Date: Tue, 6 Feb 2018 10:56:43 +0000 Subject: [PATCH] Add vfat support for the filesystem module (#23527) * Add fat filesystem support fatresize is temporarily disabled * Refactor Filesystem.get_dev_size For more sharing with vFAT class * Fix filesystem tests on some OSs I think this is due to older mke2fs on those systems. * Fix vFAT command on FreeBSD newfs doesn't seem to work on image files * Refactor filesystem.grow() Split out grow_cmd generation and Device operations * Use swap as unsupported filesystem Except FreeBSD, which doesn't have mkswap * Be consistent about str(dev) vs dev.path Prefer str(dev), this works transparently with '%s' formatting. * Enable vfat resize, only test fatresize >= 1.0.4 Lower versions have a segfault bug. * Only install fatresize where available FreeBSD, OpenSUSE, RHEL and CentOS < 7 don't ship it. --- lib/ansible/modules/system/filesystem.py | 91 +++++++++++++------ .../targets/filesystem/defaults/main.yml | 1 + .../targets/filesystem/tasks/create_fs.yml | 3 +- .../filesystem/tasks/overwrite_another_fs.yml | 9 +- .../targets/filesystem/tasks/setup.yml | 16 ++++ 5 files changed, 83 insertions(+), 37 deletions(-) diff --git a/lib/ansible/modules/system/filesystem.py b/lib/ansible/modules/system/filesystem.py index e025b30296..fd3f6e3c1b 100644 --- a/lib/ansible/modules/system/filesystem.py +++ b/lib/ansible/modules/system/filesystem.py @@ -22,12 +22,13 @@ description: version_added: "1.2" options: fstype: - choices: [ btrfs, ext2, ext3, ext4, ext4dev, lvm, reiserfs, xfs ] + choices: [ btrfs, ext2, ext3, ext4, ext4dev, lvm, reiserfs, xfs, vfat ] description: - Filesystem type to be created. - reiserfs support was added in 2.2. - lvm support was added in 2.5. - since 2.5, I(dev) can be an image file. + - vfat support was added in 2.5 required: yes dev: description: @@ -41,8 +42,9 @@ options: resizefs: description: - If C(yes), if the block device and filesytem size differ, grow the filesystem into the space. - - Supported for C(ext2), C(ext3), C(ext4), C(ext4dev), C(lvm) and C(xfs) filesystems. + - Supported for C(ext2), C(ext3), C(ext4), C(ext4dev), C(lvm), C(xfs) and C(vfat) filesystems. - XFS Will only grow if mounted. + - vFAT will likely fail if fatresize < 1.04. type: bool default: 'no' version_added: "2.0" @@ -74,8 +76,28 @@ import os import re import stat -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.six import viewkeys +from ansible.module_utils.basic import AnsibleModule, get_platform + + +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) + _, devsize_in_bytes, _ = self.module.run_command([blockdev_cmd, "--getsize64", self.path], check_rc=True) + return int(devsize_in_bytes) + elif os.path.isfile(self.path): + return os.path.getsize(self.path) + else: + self.module.fail_json(changed=False, msg="Target device not supported: %s" % self) + + def __str__(self): + return self.path class Filesystem(object): @@ -91,12 +113,6 @@ class Filesystem(object): def fstype(self): return type(self).__name__ - def get_dev_size(self, dev): - """ Return size in bytes of device. Returns int """ - blockdev_cmd = self.module.get_bin_path("blockdev", required=True) - _, devsize_in_bytes, _ = self.module.run_command("%s %s %s" % (blockdev_cmd, "--getsize64", dev), check_rc=True) - return int(devsize_in_bytes) - def get_fs_size(self, dev): """ Return size in bytes of filesystem on device. Returns int """ raise NotImplementedError() @@ -112,32 +128,26 @@ class Filesystem(object): cmd = "%s %s %s '%s'" % (mkfs, self.MKFS_FORCE_FLAGS, opts, dev) self.module.run_command(cmd, check_rc=True) + def grow_cmd(self, dev): + cmd = self.module.get_bin_path(self.GROW, required=True) + return [cmd, str(dev)] + def grow(self, dev): """Get dev and fs size and compare. Returns stdout of used command.""" - statinfo = os.stat(dev) - if stat.S_ISBLK(statinfo.st_mode): - devsize_in_bytes = self.get_dev_size(dev) - elif os.path.isfile(dev): - devsize_in_bytes = os.path.getsize(dev) - else: - self.module.fail_json(changed=False, msg="Target device not supported: %r." % dev) + devsize_in_bytes = dev.size() try: fssize_in_bytes = self.get_fs_size(dev) except NotImplementedError: self.module.fail_json(changed=False, msg="module does not support resizing %s filesystem yet." % self.fstype) - fs_smaller = fssize_in_bytes < devsize_in_bytes - if self.module.check_mode and fs_smaller: + if not fssize_in_bytes < 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)) - elif self.module.check_mode and not fs_smaller: - self.module.exit_json(changed=False, msg="%s filesystem is using the whole device %s" % (self.fstype, dev)) - elif fs_smaller: - cmd = self.module.get_bin_path(self.GROW, required=True) - _, out, _ = self.module.run_command("%s %s" % (cmd, dev), check_rc=True) - return out else: - self.module.exit_json(changed=False, msg="%s filesystem is using the whole device %s" % (self.fstype, dev)) + _, out, _ = self.module.run_command(self.grow_cmd(dev), check_rc=True) + return out class Ext(Filesystem): @@ -147,7 +157,7 @@ class Ext(Filesystem): def get_fs_size(self, dev): cmd = self.module.get_bin_path('tune2fs', required=True) # Get Block count and Block size - _, size, _ = self.module.run_command([cmd, '-l', dev], check_rc=True) + _, size, _ = self.module.run_command([cmd, '-l', str(dev)], check_rc=True) for line in size.splitlines(): if 'Block count:' in line: block_count = int(line.split(':')[1].strip()) @@ -175,7 +185,7 @@ class XFS(Filesystem): def get_fs_size(self, dev): cmd = self.module.get_bin_path('xfs_growfs', required=True) - _, size, _ = self.module.run_command([cmd, '-n', dev], check_rc=True) + _, size, _ = self.module.run_command([cmd, '-n', str(dev)], check_rc=True) for line in size.splitlines(): col = line.split('=') if col[0].strip() == 'data': @@ -215,6 +225,27 @@ class Btrfs(Filesystem): self.module.warn('Unable to identify mkfs.btrfs version (%r, %r)' % (stdout, stderr)) +class VFAT(Filesystem): + if get_platform() == 'FreeBSD': + MKFS = "newfs_msdos" + else: + MKFS = 'mkfs.vfat' + GROW = 'fatresize' + + def get_fs_size(self, dev): + cmd = self.module.get_bin_path(self.GROW, required=True) + _, output, _ = self.module.run_command([cmd, '--info', str(dev)], check_rc=True) + for line in output.splitlines()[1:]: + param, value = line.split(':', 1) + if param.strip() == 'Size': + return int(value.strip()) + self.module.fail_json(msg="fatresize failed to provide filesystem size for %s" % dev) + + def grow_cmd(self, dev): + cmd = self.module.get_bin_path(self.GROW) + return [cmd, "-s", str(dev.size()), str(dev.path)] + + class LVM(Filesystem): MKFS = 'pvcreate' MKFS_FORCE_FLAGS = '-f' @@ -222,7 +253,7 @@ class LVM(Filesystem): def get_fs_size(self, dev): cmd = self.module.get_bin_path('pvs', required=True) - _, size, _ = self.module.run_command([cmd, '--noheadings', '-o', 'pv_size', '--units', 'b', dev], check_rc=True) + _, size, _ = self.module.run_command([cmd, '--noheadings', '-o', 'pv_size', '--units', 'b', str(dev)], check_rc=True) block_count = int(size[:-1]) # block size is 1 return block_count @@ -235,6 +266,7 @@ FILESYSTEMS = { 'reiserfs': Reiserfs, 'xfs': XFS, 'btrfs': Btrfs, + 'vfat': VFAT, 'LVM2_member': LVM, } @@ -275,6 +307,7 @@ def main(): if not os.path.exists(dev): module.fail_json(msg="Device %s not found." % dev) + dev = Device(module, dev) cmd = module.get_bin_path('blkid', required=True) rc, raw_fs, err = module.run_command("%s -c /dev/null -o value -s TYPE %s" % (cmd, dev)) diff --git a/test/integration/targets/filesystem/defaults/main.yml b/test/integration/targets/filesystem/defaults/main.yml index 5399b7f651..dc5c203c4a 100644 --- a/test/integration/targets/filesystem/defaults/main.yml +++ b/test/integration/targets/filesystem/defaults/main.yml @@ -11,4 +11,5 @@ tested_filesystems: ext2: {fssize: 10, grow: True} xfs: {fssize: 20, grow: False} # grow requires a mounted filesystem btrfs: {fssize: 100, grow: False} # grow not implemented + vfat: {fssize: 20, grow: True} # untested: lvm, requires a block device diff --git a/test/integration/targets/filesystem/tasks/create_fs.yml b/test/integration/targets/filesystem/tasks/create_fs.yml index 9f585a0661..cd954f6fb1 100644 --- a/test/integration/targets/filesystem/tasks/create_fs.yml +++ b/test/integration/targets/filesystem/tasks/create_fs.yml @@ -50,7 +50,7 @@ - name: increase fake device shell: 'dd if=/dev/zero bs=1M count=20 >> {{ dev }}' - - when: 'grow|bool' + - when: 'grow|bool and (fstype != "vfat" or resize_vfat)' block: - name: Expand filesystem filesystem: @@ -81,6 +81,7 @@ - 'fs5_result is successful' - import_tasks: overwrite_another_fs.yml + when: ansible_system != 'FreeBSD' always: - file: diff --git a/test/integration/targets/filesystem/tasks/overwrite_another_fs.yml b/test/integration/targets/filesystem/tasks/overwrite_another_fs.yml index ab4bc64f95..92e3e5057f 100644 --- a/test/integration/targets/filesystem/tasks/overwrite_another_fs.yml +++ b/test/integration/targets/filesystem/tasks/overwrite_another_fs.yml @@ -1,13 +1,8 @@ - name: 'Recreate "disk" file' command: 'dd if=/dev/zero of={{ dev }} bs=1M count={{ fssize }}' -- name: 'Create a vfat filesystem' - command: 'mkfs.vfat {{ dev }}' - when: ansible_system != 'FreeBSD' - -- name: 'Create a vfat filesystem' - command: 'newfs_msdos -F12 {{ dev }}' - when: ansible_system == 'FreeBSD' +- name: 'Create a swap filesystem' + command: 'mkswap {{ dev }}' - command: 'blkid -c /dev/null -o value -s UUID {{ dev }}' register: uuid diff --git a/test/integration/targets/filesystem/tasks/setup.yml b/test/integration/targets/filesystem/tasks/setup.yml index 2e0ac385e6..8dca9f2401 100644 --- a/test/integration/targets/filesystem/tasks/setup.yml +++ b/test/integration/targets/filesystem/tasks/setup.yml @@ -31,6 +31,20 @@ - btrfsprogs when: ansible_system == 'Linux' +- block: + - name: install fatresize + package: + name: fatresize + state: present + - command: fatresize --help + register: fatresize + - set_fact: + fatresize_version: '{{ fatresize.stdout_lines[0] | regex_search("[0-9]+\.[0-9]+\.[0-9]+") }}' + when: + - ansible_system == 'Linux' + - ansible_os_family != 'Suse' + - ansible_os_family != 'RedHat' or (ansible_distribution == 'CentOS' and ansible_distribution_version is version('7.0', '>=')) + - command: mke2fs -V register: mke2fs @@ -43,3 +57,5 @@ # Mke2fs no longer complains if the user tries to create a file system # using the entire block device. force_creation: "{{ e2fsprogs_version is version('1.43', '<') }}" + # Earlier versions have a segfault bug + resize_vfat: "{{ fatresize_version|default('0.0') is version('1.0.4', '>=') }}"