#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright: (c) 2013, Alexander Bulimov # 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 ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'} DOCUMENTATION = ''' --- author: - Alexander Bulimov (@abulimov) module: filesystem short_description: Makes file system on block device description: - This module creates file system. version_added: "1.2" options: fstype: choices: [ btrfs, ext2, ext3, ext4, ext4dev, lvm, reiserfs, xfs ] description: - File System type to be created. - reiserfs support was added in 2.2. - lvm support was added in 2.5. required: yes dev: description: - Target block device. required: yes force: description: - If C(yes), allows to create new filesystem on devices that already has filesystem. type: bool default: 'no' resizefs: description: - If C(yes), if the block device and filessytem size differ, grow the filesystem into the space. - Note, XFS Will only grow if mounted. type: bool default: 'no' version_added: "2.0" opts: description: - List of options to be passed to mkfs command. notes: - Uses mkfs command. ''' EXAMPLES = ''' - name: Create a ext2 filesystem on /dev/sdb1 filesystem: fstype: ext2 dev: /dev/sdb1 - name: Create a ext4 filesystem on /dev/sdb1 and check disk blocks filesystem: fstype: ext4 dev: /dev/sdb1 opts: -cc ''' import os from ansible.module_utils.basic import AnsibleModule def _get_dev_size(dev, module): """ Return size in bytes of device. Returns int """ blockdev_cmd = module.get_bin_path("blockdev", required=True) rc, devsize_in_bytes, err = module.run_command("%s %s %s" % (blockdev_cmd, "--getsize64", dev)) return int(devsize_in_bytes) def _get_fs_size(fssize_cmd, dev, module): """ Return size in bytes of filesystem on device. Returns int """ cmd = module.get_bin_path(fssize_cmd, required=True) if 'tune2fs' == fssize_cmd: # Get Block count and Block size rc, size, err = module.run_command("%s %s %s" % (cmd, '-l', dev)) if rc == 0: for line in size.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()) break else: module.fail_json(msg="Failed to get block count and block size of %s with %s" % (dev, cmd), rc=rc, err=err) elif 'xfs_growfs' == fssize_cmd: # Get Block count and Block size rc, size, err = module.run_command([cmd, '-n', dev]) if rc == 0: for line in size.splitlines(): col = line.split('=') if col[0].strip() == 'data': if col[1].strip() != 'bsize': module.fail_json(msg='Unexpected output format from xfs_growfs (could not locate "bsize")') if col[2].split()[1] != 'blocks': module.fail_json(msg='Unexpected output format from xfs_growfs (could not locate "blocks")') block_size = int(col[2].split()[0]) block_count = int(col[3].split(',')[0]) break else: module.fail_json(msg="Failed to get block count and block size of %s with %s" % (dev, cmd), rc=rc, err=err) elif 'btrfs' == fssize_cmd: # ToDo # There is no way to get the blocksize and blockcount for btrfs filesystems block_size = 1 block_count = 1 elif 'pvs' == fssize_cmd: rc, size, err = module.run_command([cmd, '--noheadings', '-o', 'pv_size', '--units', 'b', dev]) if rc == 0: block_count = int(size[:-1]) block_size = 1 else: module.fail_json(msg="Failed to get block count and block size of %s with %s" % (dev, cmd), rc=rc, err=err) return block_size * block_count def main(): friendly_names = { 'lvm': 'LVM2_member', } # There is no "single command" to manipulate filesystems, so we map them all out and their options fs_cmd_map = { 'ext2': { 'mkfs': 'mkfs.ext2', 'grow': 'resize2fs', 'grow_flag': None, 'force_flag': '-F', 'fsinfo': 'tune2fs', }, 'ext3': { 'mkfs': 'mkfs.ext3', 'grow': 'resize2fs', 'grow_flag': None, 'force_flag': '-F', 'fsinfo': 'tune2fs', }, 'ext4': { 'mkfs': 'mkfs.ext4', 'grow': 'resize2fs', 'grow_flag': None, 'force_flag': '-F', 'fsinfo': 'tune2fs', }, 'reiserfs': { 'mkfs': 'mkfs.reiserfs', 'grow': 'resize_reiserfs', 'grow_flag': None, 'force_flag': '-f', 'fsinfo': 'reiserfstune', }, 'ext4dev': { 'mkfs': 'mkfs.ext4', 'grow': 'resize2fs', 'grow_flag': None, 'force_flag': '-F', 'fsinfo': 'tune2fs', }, 'xfs': { 'mkfs': 'mkfs.xfs', 'grow': 'xfs_growfs', 'grow_flag': None, 'force_flag': '-f', 'fsinfo': 'xfs_growfs', }, 'btrfs': { 'mkfs': 'mkfs.btrfs', 'grow': 'btrfs', 'grow_flag': 'filesystem resize', 'force_flag': '-f', 'fsinfo': 'btrfs', }, 'LVM2_member': { 'mkfs': 'pvcreate', 'grow': 'pvresize', 'grow_flag': None, 'force_flag': '-f', 'fsinfo': 'pvs', } } module = AnsibleModule( argument_spec=dict( fstype=dict(type='str', required=True, aliases=['type'], choices=fs_cmd_map.keys() + friendly_names.keys()), dev=dict(type='str', required=True, aliases=['device']), opts=dict(type='str'), force=dict(type='bool', default=False), resizefs=dict(type='bool', default=False), ), supports_check_mode=True, ) dev = module.params['dev'] fstype = module.params['fstype'] opts = module.params['opts'] force = module.boolean(module.params['force']) resizefs = module.boolean(module.params['resizefs']) if fstype in friendly_names: fstype = friendly_names[fstype] changed = False try: _ = fs_cmd_map[fstype] except KeyError: module.exit_json(changed=False, msg="WARNING: module does not support this filesystem yet. %s" % fstype) mkfscmd = fs_cmd_map[fstype]['mkfs'] force_flag = fs_cmd_map[fstype]['force_flag'] growcmd = fs_cmd_map[fstype]['grow'] fssize_cmd = fs_cmd_map[fstype]['fsinfo'] if not os.path.exists(dev): module.fail_json(msg="Device %s not found." % 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)) fs = raw_fs.strip() if fs == fstype and resizefs is False and not force: module.exit_json(changed=False) elif fs == fstype and resizefs is True: # Get dev and fs size and compare devsize_in_bytes = _get_dev_size(dev, module) fssize_in_bytes = _get_fs_size(fssize_cmd, dev, module) if fssize_in_bytes < devsize_in_bytes: fs_smaller = True else: fs_smaller = False if module.check_mode and fs_smaller: module.exit_json(changed=True, msg="Resizing filesystem %s on device %s" % (fstype, dev)) elif module.check_mode and not fs_smaller: module.exit_json(changed=False, msg="%s filesystem is using the whole device %s" % (fstype, dev)) elif fs_smaller: cmd = module.get_bin_path(growcmd, required=True) rc, out, err = module.run_command("%s %s" % (cmd, dev)) # Sadly there is no easy way to determine if this has changed. For now, just say "true" and move on. # in the future, you would have to parse the output to determine this. # thankfully, these are safe operations if no change is made. if rc == 0: module.exit_json(changed=True, msg=out) else: module.fail_json(msg="Resizing filesystem %s on device '%s' failed" % (fstype, dev), rc=rc, err=err) else: module.exit_json(changed=False, msg="%s filesystem is using the whole device %s" % (fstype, dev)) elif fs and not force: module.fail_json(msg="'%s' is already used as %s, use force=yes to overwrite" % (dev, fs), rc=rc, err=err) # create fs if module.check_mode: changed = True else: mkfs = module.get_bin_path(mkfscmd, required=True) cmd = None if opts is None: cmd = "%s %s '%s'" % (mkfs, force_flag, dev) else: cmd = "%s %s %s '%s'" % (mkfs, force_flag, opts, dev) rc, _, err = module.run_command(cmd) if rc == 0: changed = True else: module.fail_json(msg="Creating filesystem %s on device '%s' failed" % (fstype, dev), rc=rc, err=err) module.exit_json(changed=changed) if __name__ == '__main__': main()