mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Added parted module (#20399)
* Added parted module * Amended documentation * Improved documentation * Managed the case of parted not returning device information. * Fixed Shippable test not passing * Fixed compatibility with Python 2.4 * Cleanup of the state option, improved flags management, documentation refinements. * Compacted format function, code style cleanups, amended comments. * Fixed bug related to parted data parsing. * Support for check-mode, amended size data type. * Fixed Shippable test not passing. * Added full suport for CHS and CYL units, applied suggested changes.
This commit is contained in:
parent
9fe0ae082a
commit
f0de1e6c85
2 changed files with 708 additions and 0 deletions
|
@ -259,6 +259,8 @@ Ansible Changes By Release
|
||||||
* vmware_guest_snapshot
|
* vmware_guest_snapshot
|
||||||
- web_infrastructure:
|
- web_infrastructure:
|
||||||
* jenkins_script
|
* jenkins_script
|
||||||
|
- system
|
||||||
|
* parted
|
||||||
- windows:
|
- windows:
|
||||||
* win_dns_client
|
* win_dns_client
|
||||||
* win_domain_membership
|
* win_domain_membership
|
||||||
|
|
706
lib/ansible/modules/system/parted.py
Executable file
706
lib/ansible/modules/system/parted.py
Executable file
|
@ -0,0 +1,706 @@
|
||||||
|
#!/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 = {
|
||||||
|
'status': ['preview'],
|
||||||
|
'supported_by': 'committer',
|
||||||
|
'version': '1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
Loading…
Reference in a new issue