diff options
author | Ross Brattain <ross.b.brattain@intel.com> | 2017-09-28 21:45:42 -0700 |
---|---|---|
committer | Ross Brattain <ross.b.brattain@intel.com> | 2017-09-30 01:23:07 -0700 |
commit | 139480d2265d23729162054812988b0d9eca94d9 (patch) | |
tree | 34fe6e02689ffc4d4ae74f95a11a12a805d7190f /ansible/library/parted.py | |
parent | c3c086c179c35e0b27bdfe75ac3366f4fa5d6ceb (diff) |
replace ansible modules
Change-Id: Ia7c1ce781075142910a6c618a9a23f34a710dfe9
Signed-off-by: Ross Brattain <ross.b.brattain@intel.com>
Diffstat (limited to 'ansible/library/parted.py')
-rwxr-xr-x | ansible/library/parted.py | 705 |
1 files changed, 0 insertions, 705 deletions
diff --git a/ansible/library/parted.py b/ansible/library/parted.py deleted file mode 100755 index af9c80f7e..000000000 --- a/ansible/library/parted.py +++ /dev/null @@ -1,705 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2016, Fabrizio Colonna <colofabrix@tin.it> -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see <http://www.gnu.org/licenses/>. - -ANSIBLE_METADATA = {'metadata_version': '1.0', - 'status': ['preview'], - 'supported_by': 'curated'} - - -DOCUMENTATION = ''' ---- -author: - - "Fabrizio Colonna (@ColOfAbRiX)" -module: parted -short_description: Configure block device partitions -version_added: "2.3" -description: - - This module allows configuring block device partition using the C(parted) - command line tool. For a full description of the fields and the options - check the GNU parted manual. -notes: - - When fetching information about a new disk and when the version of parted - installed on the system is before version 3.1, the module queries the kernel - through C(/sys/) to obtain disk information. In this case the units CHS and - CYL are not supported. -requirements: - - This module requires parted version 1.8.3 and above. - - If the version of parted is below 3.1, it requires a Linux version running - the sysfs file system C(/sys/). -options: - device: - description: The block device (disk) where to operate. - required: True - align: - description: Set alignment for newly created partitions. - choices: ['none', 'cylinder', 'minimal', 'optimal'] - default: optimal - number: - description: - - The number of the partition to work with or the number of the partition - that will be created. Required when performing any action on the disk, - except fetching information. - unit: - description: - - Selects the current default unit that Parted will use to display - locations and capacities on the disk and to interpret those given by the - user if they are not suffixed by an unit. When fetching information about - a disk, it is always recommended to specify a unit. - choices: [ - 's', 'B', 'KB', 'KiB', 'MB', 'MiB', 'GB', 'GiB', 'TB', 'TiB', '%', 'cyl', - 'chs', 'compact' - ] - default: KiB - label: - description: Creates a new disk label. - choices: [ - 'aix', 'amiga', 'bsd', 'dvh', 'gpt', 'loop', 'mac', 'msdos', 'pc98', - 'sun', '' - ] - default: msdos - part_type: - description: - - Is one of 'primary', 'extended' or 'logical' and may be specified only - with 'msdos' or 'dvh' partition tables. A name must be specified for a - 'gpt' partition table. Neither part-type nor name may be used with a - 'sun' partition table. - choices: ['primary', 'extended', 'logical'] - part_start: - description: - - Where the partition will start as offset from the beginning of the disk, - that is, the "distance" from the start of the disk. The distance can be - specified with all the units supported by parted (except compat) and - it is case sensitive. E.g. C(10GiB), C(15%). - default: 0% - part_end : - description: - - Where the partition will end as offset from the beginning of the disk, - that is, the "distance" from the start of the disk. The distance can be - specified with all the units supported by parted (except compat) and - it is case sensitive. E.g. C(10GiB), C(15%). - default: 100% - name: - description: - - Sets the name for the partition number (GPT, Mac, MIPS and PC98 only). - flags: - description: A list of the flags that has to be set on the partition. - state: - description: - - If to create or delete a partition. If set to C(info) the module will - only return the device information. - choices: ['present', 'absent', 'info'] - default: info -''' - -RETURN = ''' -partition_info: - description: Current partition information - returned: success - type: dict - contains: - device: - description: Generic device information. - type: dict - partitions: - description: List of device partitions. - type: list - sample: > - { - "disk": { - "dev": "/dev/sdb", - "logical_block": 512, - "model": "VMware Virtual disk", - "physical_block": 512, - "size": 5.0, - "table": "msdos", - "unit": "gib" - }, - "partitions": [{ - "begin": 0.0, - "end": 1.0, - "flags": ["boot", "lvm"], - "fstype": null, - "num": 1, - "size": 1.0 - }, { - "begin": 1.0, - "end": 5.0, - "flags": [], - "fstype": null, - "num": 2, - "size": 4.0 - }] - } -''' - -EXAMPLES = """ -# Create a new primary partition -- parted: - device: /dev/sdb - number: 1 - state: present - -# Remove partition number 1 -- parted: - device: /dev/sdb - number: 1 - state: absent - -# Create a new primary partition with a size of 1GiB -- parted: - device: /dev/sdb - number: 1 - state: present - part_end: 1gib - -# Create a new primary partition for LVM -- parted: - device: /dev/sdb - number: 2 - flags: [ lvm ] - state: present - part_start: 1gib - -# Read device information (always use unit when probing) -- parted: device=/dev/sdb unit=MiB - register: sdb_info - -# Remove all partitions from disk -- parted: - device: /dev/sdb - number: "{{ item.num }}" - state: absent - with_items: - - "{{ sdb_info.partitions }}" -""" - - -from ansible.module_utils.basic import AnsibleModule -import locale -import math -import re -import os - - -# Reference prefixes (International System of Units and IEC) -units_si = ['B', 'KB', 'MB', 'GB', 'TB'] -units_iec = ['B', 'KiB', 'MiB', 'GiB', 'TiB'] -parted_units = units_si + units_iec + ['s', '%', 'cyl', 'chs', 'compact'] - - -def parse_unit(size_str, unit=''): - """ - Parses a string containing a size of information - """ - matches = re.search(r'^([\d.]+)([\w%]+)?$', size_str) - if matches is None: - # "<cylinder>,<head>,<sector>" format - matches = re.search(r'^(\d+),(\d+),(\d+)$', size_str) - if matches is None: - module.fail_json( - msg="Error interpreting parted size output: '%s'" % size_str - ) - - size = { - 'cylinder': int(matches.group(1)), - 'head': int(matches.group(2)), - 'sector': int(matches.group(3)) - } - unit = 'chs' - - else: - # Normal format: "<number>[<unit>]" - if matches.group(2) is not None: - unit = matches.group(2) - - size = float(matches.group(1)) - - return size, unit - - -def parse_partition_info(parted_output, unit): - """ - Parses the output of parted and transforms the data into - a dictionary. - - Parted Machine Parseable Output: - See: https://lists.alioth.debian.org/pipermail/parted-devel/2006-December/00 - 0573.html - - All lines end with a semicolon (;) - - The first line indicates the units in which the output is expressed. - CHS, CYL and BYT stands for CHS, Cylinder and Bytes respectively. - - The second line is made of disk information in the following format: - "path":"size":"transport-type":"logical-sector-size":"physical-sector-siz - e":"partition-table-type":"model-name"; - - If the first line was either CYL or CHS, the next line will contain - information on no. of cylinders, heads, sectors and cylinder size. - - Partition information begins from the next line. This is of the format: - (for BYT) - "number":"begin":"end":"size":"filesystem-type":"partition-name":"flags-s - et"; - (for CHS/CYL) - "number":"begin":"end":"filesystem-type":"partition-name":"flags-set"; - """ - lines = [x for x in parted_output.split('\n') if x.strip() != ''] - - # Generic device info - generic_params = lines[1].rstrip(';').split(':') - - # The unit is read once, because parted always returns the same unit - size, unit = parse_unit(generic_params[1], unit) - - generic = { - 'dev': generic_params[0], - 'size': size, - 'unit': unit.lower(), - 'table': generic_params[5], - 'model': generic_params[6], - 'logical_block': int(generic_params[3]), - 'physical_block': int(generic_params[4]) - } - - # CYL and CHS have an additional line in the output - if unit in ['cyl', 'chs']: - chs_info = lines[2].rstrip(';').split(':') - cyl_size, cyl_unit = parse_unit(chs_info[3]) - generic['chs_info'] = { - 'cylinders': int(chs_info[0]), - 'heads': int(chs_info[1]), - 'sectors': int(chs_info[2]), - 'cyl_size': cyl_size, - 'cyl_size_unit': cyl_unit.lower() - } - lines = lines[1:] - - parts = [] - for line in lines[2:]: - part_params = line.rstrip(';').split(':') - - # CHS use a different format than BYT, but contrary to what stated by - # the author, CYL is the same as BYT. I've tested this undocumented - # behaviour down to parted version 1.8.3, which is the first version - # that supports the machine parseable output. - if unit != 'chs': - size = parse_unit(part_params[3])[0] - fstype = part_params[4] - flags = part_params[5] - else: - size = "" - fstype = part_params[3] - flags = part_params[4] - - parts.append({ - 'num': int(part_params[0]), - 'begin': parse_unit(part_params[1])[0], - 'end': parse_unit(part_params[2])[0], - 'size': size, - 'fstype': fstype, - 'flags': [f.strip() for f in flags.split(', ') if f != ''], - 'unit': unit.lower(), - }) - - return {'generic': generic, 'partitions': parts} - - -def format_disk_size(size_bytes, unit): - """ - Formats a size in bytes into a different unit, like parted does. It doesn't - manage CYL and CHS formats, though. - This function has been adapted from https://github.com/Distrotech/parted/blo - b/279d9d869ff472c52b9ec2e180d568f0c99e30b0/libparted/unit.c - """ - global units_si, units_iec - - unit = unit.lower() - - # Shortcut - if size_bytes == 0: - return 0.0 - - # Cases where we default to 'compact' - if unit in ['', 'compact', 'cyl', 'chs']: - index = max(0, int( - (math.log10(size_bytes) - 1.0) / 3.0 - )) - unit = 'b' - if index < len(units_si): - unit = units_si[index] - - # Find the appropriate multiplier - multiplier = 1.0 - if unit in units_si: - multiplier = 1000.0 ** units_si.index(unit) - elif unit in units_iec: - multiplier = 1024.0 ** units_iec.index(unit) - - output = size_bytes / multiplier * (1 + 1E-16) - - # Corrections to round up as per IEEE754 standard - if output < 10: - w = output + 0.005 - elif output < 100: - w = output + 0.05 - else: - w = output + 0.5 - - if w < 10: - precision = 2 - elif w < 100: - precision = 1 - else: - precision = 0 - - # Round and return - return round(output, precision), unit - - -def get_unlabeled_device_info(device, unit): - """ - Fetches device information directly from the kernel and it is used when - parted cannot work because of a missing label. It always returns a 'unknown' - label. - """ - device_name = os.path.basename(device) - base = "/sys/block/%s" % device_name - - vendor = read_record(base + "/device/vendor", "Unknown") - model = read_record(base + "/device/model", "model") - logic_block = int(read_record(base + "/queue/logical_block_size", 0)) - phys_block = int(read_record(base + "/queue/physical_block_size", 0)) - size_bytes = int(read_record(base + "/size", 0)) * logic_block - - size, unit = format_disk_size(size_bytes, unit) - - return { - 'generic': { - 'dev': device, - 'table': "unknown", - 'size': size, - 'unit': unit, - 'logical_block': logic_block, - 'physical_block': phys_block, - 'model': "%s %s" % (vendor, model), - }, - 'partitions': [] - } - - -def get_device_info(device, unit): - """ - Fetches information about a disk and its partitions and it returns a - dictionary. - """ - global module - - # If parted complains about missing labels, it means there are no partitions. - # In this case only, use a custom function to fetch information and emulate - # parted formats for the unit. - label_needed = check_parted_label(device) - if label_needed: - return get_unlabeled_device_info(device, unit) - - command = "parted -s -m %s -- unit '%s' print" % (device, unit) - rc, out, err = module.run_command(command) - if rc != 0 and 'unrecognised disk label' not in err: - module.fail_json(msg=( - "Error while getting device information with parted " - "script: '%s'" % command), - rc=rc, out=out, err=err - ) - - return parse_partition_info(out, unit) - - -def check_parted_label(device): - """ - Determines if parted needs a label to complete its duties. Versions prior - to 3.1 don't return data when there is no label. For more information see: - http://upstream.rosalinux.ru/changelogs/libparted/3.1/changelog.html - """ - # Check the version - parted_major, parted_minor, _ = parted_version() - if (parted_major == 3 and parted_minor >= 1) or parted_major > 3: - return False - - # Older parted versions return a message in the stdout and RC > 0. - rc, out, err = module.run_command("parted -s -m %s print" % device) - if rc != 0 and 'unrecognised disk label' in out.lower(): - return True - - return False - - -def parted_version(): - """ - Returns the major and minor version of parted installed on the system. - """ - global module - - rc, out, err = module.run_command("parted --version") - if rc != 0: - module.fail_json( - msg="Failed to get parted version.", rc=rc, out=out, err=err - ) - - lines = [x for x in out.split('\n') if x.strip() != ''] - if len(lines) == 0: - module.fail_json(msg="Failed to get parted version.", rc=0, out=out) - - matches = re.search(r'^parted.+(\d+)\.(\d+)(?:\.(\d+))?$', lines[0]) - if matches is None: - module.fail_json(msg="Failed to get parted version.", rc=0, out=out) - - # Convert version to numbers - major = int(matches.group(1)) - minor = int(matches.group(2)) - rev = 0 - if matches.group(3) is not None: - rev = int(matches.group(3)) - - return major, minor, rev - - -def parted(script, device, align): - """ - Runs a parted script. - """ - global module - - if script and not module.check_mode: - command = "parted -s -m -a %s %s -- %s" % (align, device, script) - rc, out, err = module.run_command(command) - - if rc != 0: - module.fail_json( - msg="Error while running parted script: %s" % command.strip(), - rc=rc, out=out, err=err - ) - - -def read_record(file_path, default=None): - """ - Reads the first line of a file and returns it. - """ - try: - f = open(file_path, 'r') - try: - return f.readline().strip() - finally: - f.close() - except IOError: - return default - - -def part_exists(partitions, attribute, number): - """ - Looks if a partition that has a specific value for a specific attribute - actually exists. - """ - return any( - part[attribute] and - part[attribute] == number for part in partitions - ) - - -def check_size_format(size_str): - """ - Checks if the input string is an allowed size - """ - size, unit = parse_unit(size_str) - return unit in parted_units - - -def main(): - global module, units_si, units_iec - - changed = False - output_script = "" - script = "" - module = AnsibleModule( - argument_spec={ - 'device': {'required': True, 'type': 'str'}, - 'align': { - 'default': 'optimal', - 'choices': ['none', 'cylinder', 'minimal', 'optimal'], - 'type': 'str' - }, - 'number': {'default': None, 'type': 'int'}, - - # unit <unit> command - 'unit': { - 'default': 'KiB', - 'choices': parted_units, - 'type': 'str' - }, - - # mklabel <label-type> command - 'label': { - 'choices': [ - 'aix', 'amiga', 'bsd', 'dvh', 'gpt', 'loop', 'mac', 'msdos', - 'pc98', 'sun', '' - ], - 'type': 'str' - }, - - # mkpart <part-type> [<fs-type>] <start> <end> command - 'part_type': { - 'default': 'primary', - 'choices': ['primary', 'extended', 'logical'], - 'type': 'str' - }, - 'part_start': {'default': '0%', 'type': 'str'}, - 'part_end': {'default': '100%', 'type': 'str'}, - - # name <partition> <name> command - 'name': {'type': 'str'}, - - # set <partition> <flag> <state> command - 'flags': {'type': 'list'}, - - # rm/mkpart command - 'state': { - 'choices': ['present', 'absent', 'info'], - 'default': 'info', - 'type': 'str' - } - }, - supports_check_mode=True, - ) - - # Data extraction - device = module.params['device'] - align = module.params['align'] - number = module.params['number'] - unit = module.params['unit'] - label = module.params['label'] - part_type = module.params['part_type'] - part_start = module.params['part_start'] - part_end = module.params['part_end'] - name = module.params['name'] - state = module.params['state'] - flags = module.params['flags'] - - # Conditioning - if number and number < 0: - module.fail_json(msg="The partition number must be non negative.") - if not check_size_format(part_start): - module.fail_json( - msg="The argument 'part_start' doesn't respect required format." - "The size unit is case sensitive.", - err=parse_unit(part_start) - ) - if not check_size_format(part_end): - module.fail_json( - msg="The argument 'part_end' doesn't respect required format." - "The size unit is case sensitive.", - err=parse_unit(part_end) - ) - - # Read the current disk information - current_device = get_device_info(device, unit) - current_parts = current_device['partitions'] - - if state == 'present': - # Default value for the label - if not current_device['generic']['table'] or \ - current_device['generic']['table'] == 'unknown' and \ - not label: - label = 'msdos' - - # Assign label if required - if label: - script += "mklabel %s " % label - - # Create partition if required - if part_type and not part_exists(current_parts, 'num', number): - script += "mkpart %s %s %s " % ( - part_type, - part_start, - part_end - ) - - # Set the unit of the run - if unit and script: - script = "unit %s %s" % (unit, script) - - # Execute the script and update the data structure. - # This will create the partition for the next steps - if script: - output_script += script - parted(script, device, align) - changed = True - script = "" - - current_parts = get_device_info(device, unit)['partitions'] - - if part_exists(current_parts, 'num', number) or module.check_mode: - partition = {'flags': []} # Empty structure for the check-mode - if not module.check_mode: - partition = [p for p in current_parts if p['num'] == number][0] - - # Assign name to the the partition - if name: - script += "name %s %s " % (number, name) - - # Manage flags - if flags: - # Compute only the changes in flags status - flags_off = list(set(partition['flags']) - set(flags)) - flags_on = list(set(flags) - set(partition['flags'])) - - for f in flags_on: - script += "set %s %s on " % (number, f) - - for f in flags_off: - script += "set %s %s off " % (number, f) - - # Set the unit of the run - if unit and script: - script = "unit %s %s" % (unit, script) - - # Execute the script - if script: - output_script += script - changed = True - parted(script, device, align) - - elif state == 'absent': - # Remove the partition - if part_exists(current_parts, 'num', number) or module.check_mode: - script = "rm %s " % number - output_script += script - changed = True - parted(script, device, align) - - elif state == 'info': - output_script = "unit '%s' print " % unit - - # Final status of the device - final_device_status = get_device_info(device, unit) - module.exit_json( - changed=changed, - disk=final_device_status['generic'], - partitions=final_device_status['partitions'], - script=output_script.strip() - ) - - -if __name__ == '__main__': - main() |