add new module: filesize

* description: create a file given its size, or resize it
* add integration tests (filter Mac OS X < 11 for some of them)
This commit is contained in:
quidame 2021-03-06 16:21:13 +01:00
parent 355a99f779
commit 44d071cb4c
9 changed files with 1257 additions and 0 deletions

439
plugins/modules/filesize.py Normal file
View file

@ -0,0 +1,439 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, quidame <quidame@poivron.org>
# 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
DOCUMENTATION = r'''
---
module: filesize
short_description: Create a file with a given size, or resize it if it exists.
description:
- This module is a simple wrapper around C(dd) to create, extend or truncate
a file, given its size. It can be used to manage swap files (that require
contiguous blocks) or alternatively, huge sparse files.
author:
- quidame (@quidame)
version_added: "1.2.0"
options:
path:
description:
- Path of the regular file to create or resize.
type: path
required: yes
size:
description:
- Requested size of the file.
- The value is a number (either C(int) or C(float)) optionally followed
by a multiplicative suffix, that can be one of C(B) (bytes), C(KB) or
C(kB) (= 1000B), C(MB) or C(mB) (= 1000kB), C(GB) or C(gB) (= 1000MB),
and so on for C(T), C(P), C(E), C(Z) and C(Y); or alternatively one of
C(K), C(k), C(KiB) or C(kiB) (= 1024B); C(M), C(m), C(MiB) or C(miB)
(= 1024KiB); C(G), C(g), C(GiB) or C(giB) (= 1024MiB); and so on.
- If the multiplicative suffix is not provided, the value is treated as
an integer number of blocks of I(blocksize) bytes each (float values
will be rounded to the closest integer).
- When the I(size) value is equal to the current file size, does nothing.
- When the I(size) value is bigger than the current file size, bytes will
be appended to the file without truncating it, i.e. without modifying
the existing bytes of the file.
- When the I(size) value is smaller than the current file size, it will
be truncated to the requested value without modifyng bytes before this
value.
- That means that a file of any arbitrary size can be grown to any other
arbitrary size, and then resized down to its initial size without
modifying its initial content.
type: str
required: yes
blocksize:
description:
- Size of blocks, in bytes if not followed by a multiplicative suffix.
- The numeric value (before the unit) C(MUST) be an integer (or a C(float)
if it equals an integer).
- If not set, the size of blocks will be guessed from the OS and commonly
result in C(512) or C(4096) bytes, that will be used internally by the
module or when I(size) has no unit.
type: str
source:
description:
- Device or file that will provide input data to provision the file.
- This parameter is ignored when I(sparse=yes).
type: path
default: /dev/zero
force:
description:
- Whether or not to overwrite the file if it exists, i.e. to truncate it
from 0. When C(true), the module is not idempotent.
- I(force=yes) and I(sparse=yes) are mutually exclusive.
type: bool
default: no
sparse:
description:
- Whether or not the file to create should be a sparse file.
- This option is effective only on newly created files, or when growing a
file, only for the bytes to append.
- This option is not supported on OpenBSD and Solaris.
- I(force=yes) and I(sparse=yes) are mutually exclusive.
type: bool
default: no
notes:
- This module supports I(check_mode) and I(diff).
requirements:
- dd
extends_documentation_fragment: files
'''
EXAMPLES = r'''
- name: Create a file of 1G filled with null bytes
ansible.posix.filesize:
path: /var/bigfile
size: 1G
- name: Extend the file to 2G (2*1024^3)
ansible.posix.filesize:
path: /var/bigfile
size: 2G
- name: Reduce the file to 2GB (2*1000^3)
ansible.posix.filesize:
path: /var/bigfile
size: 2GB
- name: Fill a file with random bytes for backing a luks device
ansible.posix.filesize:
path: ~/diskimage.luks
size: 512.0 MiB
source: /dev/urandom
- name: Take a backup of MBR boot code into a file, overwritting it if it exists
ansible.posix.filesize:
path: /media/sdb1/mbr.bin
size: 440B
source: /dev/sda
force: yes
- name: Create/resize a sparse file of/to 8TB
ansible.posix.filesize:
path: /var/local/sparsefile
size: 8TB
sparse: yes
- name: Create a file with specific size and attributes, to be used as swap space
ansible.posix.filesize:
path: /var/swapfile
size: 2G
blocksize: 512B
mode: u=rw,go=
owner: root
group: root
'''
RETURN = r'''
cmd:
description: Command executed to create or resize the file.
type: str
returned: when changed or failed
sample: dd if=/dev/zero of=/var/swapfile bs=1048576 seek=3072 count=1024 conv=fsync
filesize:
description: Dictionary of sizes related to the file.
type: dict
returned: always
contains:
blocks:
description: Number of blocks in the file.
type: int
sample: 500
blocksize:
description: Size of the blocks.
type: int
sample: 1024
bytes:
description: Size of the file, in bytes, as the product of C(blocks) and C(blocksize)).
type: int
sample: 512000
iec:
description: Size of the file, in human-readable format, following IEC standard.
type: str
sample: 500.0 KiB
si:
description: Size of the file, in human-readable format, following SI standard.
type: str
sample: 512.0 kB
size_diff:
description: Difference (positive or negative) between old size and new size, in bytes.
type: int
sample: -1234567890
returned: always
path:
description: Realpath of the file if it is a symlink, otherwize the same than module's param.
type: str
sample: /var/swap0
returned: always
'''
import re
import os
import math
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
# These are the multiplicative suffixes understood (or returned) by dd and
# others (ls, df, lvresize, lsblk...).
SIZE_UNITS = dict(
B=1,
kB=1000**1, KB=1000**1, KiB=1024**1, kiB=1024**1, K=1024**1, k=1024**1,
MB=1000**2, mB=1000**2, MiB=1024**2, miB=1024**2, M=1024**2, m=1024**2,
GB=1000**3, gB=1000**3, GiB=1024**3, giB=1024**3, G=1024**3, g=1024**3,
TB=1000**4, tB=1000**4, TiB=1024**4, tiB=1024**4, T=1024**4, t=1024**4,
PB=1000**5, pB=1000**5, PiB=1024**5, piB=1024**5, P=1024**5, p=1024**5,
EB=1000**6, eB=1000**6, EiB=1024**6, eiB=1024**6, E=1024**6, e=1024**6,
ZB=1000**7, zB=1000**7, ZiB=1024**7, ziB=1024**7, Z=1024**7, z=1024**7,
YB=1000**8, yB=1000**8, YiB=1024**8, yiB=1024**8, Y=1024**8, y=1024**8,
)
def bytes_to_human(size, iec=False):
"""Return human-readable size (with SI or IEC suffix) from bytes. This is
only to populate the returned result of the module, not to handle the
file itself (we only rely on bytes for that).
"""
unit = 'B'
for (u, v) in SIZE_UNITS.items():
if size < v:
continue
if iec:
if 'i' not in u or size / v >= 1024:
continue
else:
if v % 5 or size / v >= 1000:
continue
unit = u
hsize = round(size / SIZE_UNITS[unit], 2)
if unit == 'B':
hsize = int(hsize)
unit = re.sub(r'^(.)', lambda m: m.expand(r'\1').upper(), unit)
if unit == 'KB':
unit = 'kB'
return '%s %s' % (str(hsize), unit)
def smart_blocksize(size, unit, product, bsize):
"""Ensure the total size can be written as blocks*blocksize, with blocks
and blocksize being integers.
"""
if not product % bsize:
return bsize
# Basically, for a file of 8kB (=8000B), system's block size of 4096 bytes
# is not usable. The smallest integer number of kB to work with 512B blocks
# is 64, the nexts are 128, 192, 256, and so on.
unit_size = SIZE_UNITS[unit]
if size == int(size):
if unit_size > SIZE_UNITS['MiB']:
if unit_size % 5:
return SIZE_UNITS['MiB']
return SIZE_UNITS['MB']
return unit_size
if unit == 'B':
raise AssertionError("byte is the smallest unit and requires an integer value")
if 0 < product < bsize:
return product
for bsz in (1024, 1000, 512, 256, 128, 100, 64, 32, 16, 10, 8, 4, 2):
if not product % bsz:
return bsz
return 1
def split_size_unit(string, isint=False):
"""Split a string between the size value (int or float) and the unit.
Support optional space(s) between the numeric value and the unit.
"""
unit = re.sub(r'(\d|\.)', r'', string).strip()
value = float(re.sub(r'%s' % unit, r'', string).strip())
if isint and unit in ('B', ''):
if int(value) != value:
raise AssertionError("invalid blocksize value: bytes require an integer value")
if not unit:
unit = None
product = int(round(value))
else:
if unit not in SIZE_UNITS.keys():
raise AssertionError("invalid size unit (%s): unit must be one of %s, or none." %
(unit, ', '.join(sorted(SIZE_UNITS, key=SIZE_UNITS.get))))
product = int(round(value * SIZE_UNITS[unit]))
return value, unit, product
def size_spec(args):
"""Return a dictionary with size specifications, especially the size in
bytes (after rounding it to an integer number of blocks).
"""
blocksize_in_bytes = split_size_unit(args['blocksize'], True)[2]
if blocksize_in_bytes == 0:
raise AssertionError("block size cannot be equal to zero")
size_value, size_unit, size_result = split_size_unit(args['size'])
if not size_unit:
blocks = int(math.ceil(size_value))
else:
blocksize_in_bytes = smart_blocksize(size_value, size_unit, size_result, blocksize_in_bytes)
blocks = int(math.ceil(size_result / blocksize_in_bytes))
args['size_diff'] = round_bytes = int(blocks * blocksize_in_bytes)
args['size_spec'] = dict(blocks=blocks, blocksize=blocksize_in_bytes, bytes=round_bytes,
iec=bytes_to_human(round_bytes, True),
si=bytes_to_human(round_bytes))
return args['size_spec']
def current_size(args):
"""Return the size of the file at the given location if it exists, or None."""
path = args['path']
if os.path.exists(path):
if not os.path.isfile(path):
raise AssertionError("%s exists but is not a regular file" % path)
args['file_size'] = os.stat(path).st_size
else:
args['file_size'] = None
return args['file_size']
def complete_dd_cmdline(args, dd_cmd):
"""Compute dd options to grow or truncate a file."""
if args['file_size'] == args['size_spec']['bytes'] and not args['force']:
# Nothing to do.
return list()
bs = args['size_spec']['blocksize']
conv = list()
# For sparse files (create, truncate, grow): write count=0 block.
if args['sparse']:
seek = args['size_spec']['blocks']
conv += ['sparse']
elif args['force'] or not os.path.exists(args['path']): # Create file
seek = 0
elif args['size_diff'] < 0: # Truncate file
seek = args['size_spec']['blocks']
elif args['size_diff'] % bs: # Grow file
seek = int(args['file_size'] / bs) + 1
else:
seek = int(args['file_size'] / bs)
count = args['size_spec']['blocks'] - seek
dd_cmd += ['bs=%s' % str(bs), 'seek=%s' % str(seek), 'count=%s' % str(count)]
if conv:
dd_cmd += ['conv=%s' % ','.join(conv)]
return dd_cmd
def main():
module = AnsibleModule(
argument_spec=dict(
path=dict(type='path', required=True),
size=dict(type='str', required=True),
blocksize=dict(type='str'),
source=dict(type='path', default='/dev/zero'),
sparse=dict(type='bool', default=False),
force=dict(type='bool', default=False),
),
supports_check_mode=True,
add_file_common_args=True,
)
args = dict(**module.params)
diff = dict(before=dict(), after=dict())
if args['sparse'] and args['force']:
module.fail_json(msg='parameters values are mutually exclusive: force=true|sparse=true')
if not os.path.exists(os.path.dirname(args['path'])):
module.fail_json(msg='parent directory of the file must exist prior to run this module')
if not args['blocksize']:
args['blocksize'] = str(os.statvfs(os.path.dirname(args['path'])).f_frsize)
try:
initial_filesize = current_size(args)
size_descriptors = size_spec(args)
except AssertionError as err:
module.fail_json(msg=to_native(err))
expected_filesize = size_descriptors['bytes']
if initial_filesize:
args['size_diff'] = expected_filesize - initial_filesize
diff['after']['size'] = expected_filesize
diff['before']['size'] = initial_filesize
result = dict(
changed=args['force'],
size_diff=args['size_diff'],
path=args['path'],
filesize=size_descriptors)
dd_bin = module.get_bin_path('dd', True)
dd_cmd = list([dd_bin, 'if=%s' % args['source'], 'of=%s' % args['path']])
if expected_filesize != initial_filesize or args['force']:
result['cmd'] = ' '.join(complete_dd_cmdline(args, dd_cmd))
if module.check_mode:
result['changed'] = True
else:
result['rc'], dummy, result['stderr'] = module.run_command(dd_cmd)
diff['after']['size'] = result_filesize = result['size_diff'] = current_size(args)
if initial_filesize:
result['size_diff'] = result_filesize - initial_filesize
if not args['force']:
result['changed'] = result_filesize != initial_filesize
if result['rc']:
msg = "dd error while creating file %s with size %s from source %s: see stderr for details" % (
args['path'], args['size'], args['source'])
module.fail_json(msg=msg, **result)
if result_filesize != expected_filesize:
msg = "module error while creating file %s with size %s from source %s: file is %s bytes long" % (
args['path'], args['size'], args['source'], result_filesize)
module.fail_json(msg=msg, **result)
# dd follows symlinks, and so does this module, while file module doesn't.
# If we call it, this is to manage file's mode, owner and so on, not the
# symlink's ones.
file_params = dict(**module.params)
if os.path.islink(args['path']):
file_params['path'] = result['path'] = os.path.realpath(args['path'])
if args['file_size'] is not None:
file_args = module.load_file_common_arguments(file_params)
result['changed'] = module.set_fs_attributes_if_different(file_args, result['changed'], diff=diff)
result['diff'] = diff
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1 @@
shippable/posix/group1

View file

@ -0,0 +1,4 @@
---
filesize_testdir: "/tmp/testdir"
filesize_testfile: "{{ filesize_testdir }}/testfile"
filesize_testlink: "{{ filesize_testdir }}/testlink"

View file

@ -0,0 +1,285 @@
---
# Test module with basic parameters.
# Create a file, grow it, reduce it to its initial size and check the match
# between initial and final checksums. Also check size formats consistency
# (as 57001B == 57001 B == 57.001 kB, for example, or 0 block or 0 unit is
# zero, etc).
- name: Create an empty file (check mode)
filesize:
path: "{{ filesize_testfile }}"
size: "0"
register: filesize_test_basic_01
check_mode: yes
- name: Create an empty file
filesize:
path: "{{ filesize_testfile }}"
size: "0"
register: filesize_test_basic_02
- name: Create an empty file (check mode, idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: "0G"
register: filesize_test_basic_03
check_mode: yes
- name: Create an empty file (idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: "0G"
register: filesize_test_basic_04
- name: Assert that results are as expected
assert:
that:
- filesize_test_basic_01 is changed
- filesize_test_basic_02 is changed
- filesize_test_basic_03 is not changed
- filesize_test_basic_04 is not changed
- filesize_test_basic_01.state is undefined
- filesize_test_basic_02.state in ["file"]
- filesize_test_basic_01.size is undefined
- filesize_test_basic_02.size == 0
- filesize_test_basic_03.size == 0
- filesize_test_basic_04.size == 0
- name: Fill the file up to 57kB (57000B) with random data (check mode)
filesize:
path: "{{ filesize_testfile }}"
size: "57kB"
source: /dev/urandom
register: filesize_test_basic_11
check_mode: yes
- name: Fill the file up to 57kB (57000B) with random data
filesize:
path: "{{ filesize_testfile }}"
size: "57kB"
source: /dev/urandom
register: filesize_test_basic_12
- name: Get checksum of the resulting file
stat:
path: "{{ filesize_testfile }}"
register: filesize_test_basic_stat_00
- name: Store checksum as fact
set_fact:
filesize_test_checksum: "{{ filesize_test_basic_stat_00.stat.checksum }}"
- name: Fill the file up to 57000B (57kB) with random data (check mode, idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: "57000B"
source: /dev/urandom
register: filesize_test_basic_13
check_mode: yes
- name: Fill the file up to 57000B (57kB) with random data (idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: "57000B"
source: /dev/urandom
register: filesize_test_basic_14
- name: Get checksum of the resulting file
stat:
path: "{{ filesize_testfile }}"
register: filesize_test_basic_stat_01
- name: Assert that results are as expected
assert:
that:
- filesize_test_basic_11 is changed
- filesize_test_basic_12 is changed
- filesize_test_basic_13 is not changed
- filesize_test_basic_14 is not changed
- filesize_test_basic_11.filesize.bytes == 57000
- filesize_test_basic_12.filesize.bytes == 57000
- filesize_test_basic_13.filesize.bytes == 57000
- filesize_test_basic_14.filesize.bytes == 57000
- filesize_test_basic_11.size == 0
- filesize_test_basic_12.size == 57000
- filesize_test_basic_13.size == 57000
- filesize_test_basic_14.size == 57000
- filesize_test_basic_stat_01.stat.checksum == filesize_test_checksum
- name: Expand the file with 1 byte (57001B) (check mode)
filesize:
path: "{{ filesize_testfile }}"
size: "57001B"
register: filesize_test_basic_21
check_mode: yes
- name: Expand the file with 1 byte (57001B)
filesize:
path: "{{ filesize_testfile }}"
size: "57001B"
register: filesize_test_basic_22
- name: Get checksum of the resulting file
stat:
path: "{{ filesize_testfile }}"
register: filesize_test_basic_stat_02
- name: Expand the file with 1 byte (57.001kB) (check mode, idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: "57.001 kB"
register: filesize_test_basic_23
check_mode: yes
- name: Expand the file with 1 byte (57.001kB) (idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: "57.001 kB"
register: filesize_test_basic_24
- name: Assert that results are as expected
assert:
that:
- filesize_test_basic_21 is changed
- filesize_test_basic_22 is changed
- filesize_test_basic_23 is not changed
- filesize_test_basic_24 is not changed
- filesize_test_basic_21.filesize.bytes == 57001
- filesize_test_basic_22.filesize.bytes == 57001
- filesize_test_basic_23.filesize.bytes == 57001
- filesize_test_basic_24.filesize.bytes == 57001
- filesize_test_basic_21.size == 57000
- filesize_test_basic_22.size == 57001
- filesize_test_basic_23.size == 57001
- filesize_test_basic_24.size == 57001
- filesize_test_basic_21.size_diff == 1
- filesize_test_basic_22.size_diff == 1
- filesize_test_basic_23.size_diff == 0
- filesize_test_basic_24.size_diff == 0
- filesize_test_basic_stat_02.stat.checksum != filesize_test_checksum
- name: Expand the file up to 2 MiB (2*1024*1024 bytes) (check mode)
filesize:
path: "{{ filesize_testfile }}"
size: "2 MiB"
register: filesize_test_basic_31
check_mode: yes
- name: Expand the file up to 2 MiB (2*1024*1024 bytes)
filesize:
path: "{{ filesize_testfile }}"
size: "2 MiB"
register: filesize_test_basic_32
- name: Expand the file up to 2×1M (2*1024*1024 bytes) (check mode, idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: "2"
blocksize: "1M"
register: filesize_test_basic_33
check_mode: yes
- name: Expand the file up to 2×1M (2*1024*1024 bytes) (idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: "2"
blocksize: "1M"
register: filesize_test_basic_34
- name: Assert that results are as expected
assert:
that:
- filesize_test_basic_31 is changed
- filesize_test_basic_32 is changed
- filesize_test_basic_33 is not changed
- filesize_test_basic_34 is not changed
- filesize_test_basic_31.filesize.bytes == 2*1024**2
- filesize_test_basic_32.filesize.bytes == 2*1024**2
- filesize_test_basic_33.filesize.bytes == 2*1024**2
- filesize_test_basic_34.filesize.bytes == 2*1024**2
- filesize_test_basic_31.size == 57001
- filesize_test_basic_32.size == 2*1024**2
- filesize_test_basic_33.size == 2*1024**2
- filesize_test_basic_34.size == 2*1024**2
- filesize_test_basic_31.size_diff == 2*1024**2 - 57001
- filesize_test_basic_32.size_diff == 2*1024**2 - 57001
- filesize_test_basic_33.size_diff == 0
- filesize_test_basic_34.size_diff == 0
- name: Truncate the file to 57kB (57000B) (check mode)
filesize:
path: "{{ filesize_testfile }}"
size: "57kB"
register: filesize_test_basic_41
check_mode: yes
- name: Truncate the file to 57kB (57000B)
filesize:
path: "{{ filesize_testfile }}"
size: "57kB"
register: filesize_test_basic_42
- name: Get checksum of the resulting file
stat:
path: "{{ filesize_testfile }}"
register: filesize_test_basic_stat_03
- name: Truncate the file to 57000B (57kB) (check mode, idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: "57000 B"
register: filesize_test_basic_43
check_mode: yes
- name: Truncate the file to 57000B (57kB) (idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: "57000 B"
register: filesize_test_basic_44
- name: Get checksum of the resulting file
stat:
path: "{{ filesize_testfile }}"
register: filesize_test_basic_stat_04
- name: Assert that results are as expected
assert:
that:
- filesize_test_basic_41 is changed
- filesize_test_basic_42 is changed
- filesize_test_basic_43 is not changed
- filesize_test_basic_44 is not changed
- filesize_test_basic_41.filesize.bytes == 57000
- filesize_test_basic_42.filesize.bytes == 57000
- filesize_test_basic_43.filesize.bytes == 57000
- filesize_test_basic_44.filesize.bytes == 57000
- filesize_test_basic_41.size == filesize_test_basic_31.filesize.bytes
- filesize_test_basic_42.size == 57000
- filesize_test_basic_43.size == 57000
- filesize_test_basic_44.size == 57000
- filesize_test_basic_stat_03.stat.checksum == filesize_test_checksum
- filesize_test_basic_stat_04.stat.checksum == filesize_test_checksum
- name: Remove test file
file:
path: "{{ filesize_testfile }}"
state: absent

View file

@ -0,0 +1,117 @@
---
# Check error handling of the module.
# 1. Missing or unknown parameters
# 2. Wrong values (missing source device, invalid size...)
- name: Trigger an error due to missing parameter (path)
filesize:
size: 1kB
register: filesize_test_error_01
ignore_errors: yes
- name: Trigger an error due to missing parameter (size)
filesize:
path: "{{ filesize_testfile }}"
register: filesize_test_error_02
ignore_errors: yes
- name: Trigger an error due to conflicting parameters (force|sparse)
filesize:
path: "{{ filesize_testfile }}"
size: 1MB
force: yes
sparse: yes
register: filesize_test_error_03
ignore_errors: yes
- name: Trigger an error due to invalid file path (not a file)
filesize:
path: "{{ filesize_testdir }}"
size: 4096B
register: filesize_test_error_04
ignore_errors: yes
- name: Trigger an error due to invalid file path (unexisting parent dir)
filesize:
path: "/unexistent/{{ filesize_testfile }}"
size: 4096B
register: filesize_test_error_05
ignore_errors: yes
- name: Trigger an error due to invalid size unit (b)"
filesize:
path: "{{ filesize_testfile }}"
size: 4096b
register: filesize_test_error_06
ignore_errors: yes
- name: Trigger an error due to invalid size value (bytes require integer)
filesize:
path: "{{ filesize_testfile }}"
size: 1000.5B
register: filesize_test_error_07
ignore_errors: yes
- name: Trigger an error due to invalid blocksize value (not an integer)
filesize:
path: "{{ filesize_testfile }}"
size: 1M
blocksize: "12.5"
register: filesize_test_error_08
ignore_errors: yes
- name: Trigger an error due to invalid source device (/dev/unexistent)
filesize:
path: "{{ filesize_testfile }}"
size: 1M
source: /dev/unexistent
register: filesize_test_error_09
ignore_errors: yes
- name: Trigger an error due to invalid source device (/dev/null)
filesize:
path: "{{ filesize_testfile }}"
size: 1M
source: /dev/null
register: filesize_test_error_10
ignore_errors: yes
- name: Assert that expected errors have been triggered
assert:
that:
- "filesize_test_error_01 is failed"
- "filesize_test_error_01.msg == 'missing required arguments: path'"
- "filesize_test_error_02 is failed"
- "filesize_test_error_02.msg == 'missing required arguments: size'"
- "filesize_test_error_03 is failed"
- "filesize_test_error_03.msg == 'parameters values are mutually exclusive: force=true|sparse=true'"
- "filesize_test_error_04 is failed"
- "filesize_test_error_04.msg == '%s exists but is not a regular file' % filesize_testdir"
- "filesize_test_error_05 is failed"
- "filesize_test_error_05.msg == 'parent directory of the file must exist prior to run this module'"
- "filesize_test_error_06 is failed"
- "filesize_test_error_06.msg is match('invalid size unit')"
- "filesize_test_error_07 is failed"
- "filesize_test_error_07.msg == 'byte is the smallest unit and requires an integer value'"
- "filesize_test_error_08 is failed"
- "filesize_test_error_08.msg == 'invalid blocksize value: bytes require an integer value'"
- "filesize_test_error_09 is failed"
- "filesize_test_error_09.msg == 'dd error while creating file %s with size 1M from source /dev/unexistent: see stderr for details' % filesize_testfile"
- "filesize_test_error_10 is failed"
- "filesize_test_error_10.msg == 'module error while creating file %s with size 1M from source /dev/null: file is 0 bytes long' % filesize_testfile"
- name: Remove test file
file:
path: "{{ filesize_testfile }}"
state: absent

View file

@ -0,0 +1,138 @@
---
# Test module with floating point numbers (ensure they're not rounded too
# wrongly), since in python floats are tricky:
# 256.256 * 1000 == 256255.9999999997
# 512.512 * 1000 == 512511.9999999994
# 512.513 * 1000 == 512513.0000000006 != .512513 * 1000000
- name: Create a file with a size of 512.512kB (check mode)
filesize:
path: "{{ filesize_testfile }}"
size: 512.512kB
register: filesize_test_float_01
check_mode: yes
- name: Create a file with a size of 512.512kB
filesize:
path: "{{ filesize_testfile }}"
size: 512.512kB
register: filesize_test_float_02
- name: Create a file with a size of 0.512512MB (check mode, idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: 0.512512MB
register: filesize_test_float_03
check_mode: yes
- name: Create a file with a size of 0.512512MB (idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: 0.512512MB
register: filesize_test_float_04
- name: Assert that results are as expected
assert:
that:
- filesize_test_float_01 is changed
- filesize_test_float_02 is changed
- filesize_test_float_03 is not changed
- filesize_test_float_04 is not changed
- filesize_test_float_01.state is undefined
- filesize_test_float_02.state in ["file"]
- filesize_test_float_01.size is undefined
- filesize_test_float_02.size == 512512
- filesize_test_float_03.size == 512512
- filesize_test_float_04.size == 512512
- name: Create a file with a size of 512.513kB (check mode)
filesize:
path: "{{ filesize_testfile }}"
size: 512.513kB
register: filesize_test_float_11
check_mode: yes
- name: Create a file with a size of 512.513kB
filesize:
path: "{{ filesize_testfile }}"
size: 512.513kB
register: filesize_test_float_12
- name: Create a file with a size of 0.512513MB (check mode, idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: 0.512513MB
register: filesize_test_float_13
check_mode: yes
- name: Create a file with a size of 0.512513MB (idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: 0.512513MB
register: filesize_test_float_14
- name: Assert that results are as expected
assert:
that:
- filesize_test_float_11 is changed
- filesize_test_float_12 is changed
- filesize_test_float_13 is not changed
- filesize_test_float_14 is not changed
- filesize_test_float_11.size == 512512
- filesize_test_float_11.size_diff == 1
- filesize_test_float_12.size_diff == 1
- filesize_test_float_12.size == 512513
- filesize_test_float_13.size == 512513
- filesize_test_float_14.size == 512513
- name: Create a file with a size of 4.004MB (check mode)
filesize:
path: "{{ filesize_testfile }}"
size: 4.004MB
register: filesize_test_float_21
check_mode: yes
- name: Create a file with a size of 4.004MB
filesize:
path: "{{ filesize_testfile }}"
size: 4.004MB
register: filesize_test_float_22
- name: Create a file with a size of 4.004MB (check mode, idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: 4.004MB
register: filesize_test_float_23
check_mode: yes
- name: Create a file with a size of 4.004MB (idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: 4.004MB
register: filesize_test_float_24
- name: Assert that results are as expected
assert:
that:
- filesize_test_float_21 is changed
- filesize_test_float_22 is changed
- filesize_test_float_23 is not changed
- filesize_test_float_24 is not changed
- filesize_test_float_21.size == 512513
- filesize_test_float_22.size == 4004000
- filesize_test_float_23.size == 4004000
- filesize_test_float_24.size == 4004000
- name: Remove test file
file:
path: "{{ filesize_testfile }}"
state: absent

View file

@ -0,0 +1,35 @@
---
- name: Ensure the test dir is present
file:
path: "{{ filesize_testdir }}"
state: directory
- name: Ensure the test file is absent
file:
path: "{{ filesize_testfile }}"
state: absent
- name: Run all tests and remove the workspace anyway
block:
- name: Include tasks to test error handling
include_tasks: errors.yml
- name: Include tasks to test basic behaviours
include_tasks: basics.yml
- name: Include tasks to test playing with floating point numbers
include_tasks: floats.yml
- name: Include tasks to test playing with sparse files
include_tasks: sparse.yml
when:
- not (ansible_os_family == 'Darwin' and ansible_distribution_version is version('11', '<'))
- name: Include tasks to test playing with symlinks
include_tasks: symlinks.yml
always:
- name: Remove test dir
file:
path: "{{ filesize_testdir }}"
state: absent

View file

@ -0,0 +1,167 @@
---
# Test module with sparse files
- name: Create a huge sparse file of 4TB (check mode)
filesize:
path: "{{ filesize_testfile }}"
size: "4TB"
sparse: yes
register: filesize_test_sparse_01
check_mode: yes
- name: Create a huge sparse file of 4TB
filesize:
path: "{{ filesize_testfile }}"
size: "4TB"
sparse: yes
register: filesize_test_sparse_02
- name: Create a huge sparse file of 4TB (4000GB) (check mode, idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: "4000GB"
sparse: yes
register: filesize_test_sparse_03
check_mode: yes
- name: Create a huge sparse file of 4TB (4000GB) (idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: "4000GB"
sparse: yes
register: filesize_test_sparse_04
- name: Create a huge sparse file of 4TB (4000000 × 1MB) (check mode, idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: "4000000"
blocksize: 1MB
sparse: yes
register: filesize_test_sparse_05
check_mode: yes
- name: Create a huge sparse file of 4TB (4000000 × 1MB) (idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: "4000000"
blocksize: 1MB
sparse: yes
register: filesize_test_sparse_06
- name: Assert that results are as expected
assert:
that:
- filesize_test_sparse_01 is changed
- filesize_test_sparse_02 is changed
- filesize_test_sparse_03 is not changed
- filesize_test_sparse_04 is not changed
- filesize_test_sparse_05 is not changed
- filesize_test_sparse_06 is not changed
- filesize_test_sparse_01.state is undefined
- filesize_test_sparse_02.state in ["file"]
- filesize_test_sparse_01.size is undefined
- filesize_test_sparse_02.size == 4000000000000
- filesize_test_sparse_03.size == 4000000000000
- filesize_test_sparse_04.size == 4000000000000
- filesize_test_sparse_05.size == 4000000000000
- filesize_test_sparse_06.size == 4000000000000
- name: Change sparse file size to 4TiB (check mode)
filesize:
path: "{{ filesize_testfile }}"
size: 4TiB
sparse: yes
register: filesize_test_sparse_11
check_mode: yes
- name: Change sparse file size to 4TiB
filesize:
path: "{{ filesize_testfile }}"
size: 4TiB
sparse: yes
register: filesize_test_sparse_12
- name: Change sparse file size to 4TiB (4096GiB) (check mode, idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: 4096GiB
sparse: yes
register: filesize_test_sparse_13
check_mode: yes
- name: Change sparse file size to 4TiB (4096GiB) (idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: 4096GiB
sparse: yes
register: filesize_test_sparse_14
- name: Assert that results are as expected
assert:
that:
- filesize_test_sparse_11 is changed
- filesize_test_sparse_12 is changed
- filesize_test_sparse_13 is not changed
- filesize_test_sparse_14 is not changed
- filesize_test_sparse_11.size == 4000000000000
- filesize_test_sparse_12.size == 4398046511104
- filesize_test_sparse_13.size == 4398046511104
- filesize_test_sparse_14.size == 4398046511104
- name: Change sparse file size to 4.321TB (check mode)
filesize:
path: "{{ filesize_testfile }}"
size: 4.321TB
sparse: yes
register: filesize_test_sparse_21
check_mode: yes
- name: Change sparse file size to 3.211TB
filesize:
path: "{{ filesize_testfile }}"
size: 4.321TB
sparse: yes
register: filesize_test_sparse_22
- name: Change sparse file size to 3211×1GB (check mode, idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: "4321"
blocksize: 1GB
sparse: yes
register: filesize_test_sparse_23
check_mode: yes
- name: Change sparse file size to 3211×1GB (idempotency)
filesize:
path: "{{ filesize_testfile }}"
size: "4321"
blocksize: 1GB
sparse: yes
register: filesize_test_sparse_24
- name: Assert that results are as expected
assert:
that:
- filesize_test_sparse_21 is changed
- filesize_test_sparse_22 is changed
- filesize_test_sparse_23 is not changed
- filesize_test_sparse_24 is not changed
- filesize_test_sparse_21.size == 4398046511104
- filesize_test_sparse_22.size == 4321000000000
- filesize_test_sparse_23.size == 4321000000000
- filesize_test_sparse_24.size == 4321000000000
- name: Remove test file
file:
path: "{{ filesize_testfile }}"
state: absent

View file

@ -0,0 +1,71 @@
---
# Check that the module works with symlinks, as expected, i.e. as dd does:
# follow symlinks.
- name: Ensure the test file is absent
file:
path: "{{ filesize_testfile }}"
state: absent
- name: Create a broken symlink in the same directory
file:
src: "{{ filesize_testfile | basename }}"
dest: "{{ filesize_testlink }}"
state: link
force: yes
follow: no
- name: Create a file with a size of 512 kB (512000 bytes) (check mode)
filesize:
path: "{{ filesize_testlink }}"
size: "512 kB"
register: filesize_test_symlink_01
check_mode: yes
- name: Create a file with a size of 512 kB (512000 bytes)
filesize:
path: "{{ filesize_testlink }}"
size: "512 kB"
register: filesize_test_symlink_02
- name: Create a file with a size of 500 KiB (512000 bytes) (check mode, idempotency)
filesize:
path: "{{ filesize_testlink }}"
size: "500 KiB"
register: filesize_test_symlink_03
check_mode: yes
- name: Create a file with a size of 500 KiB (512000 bytes) (idempotency)
filesize:
path: "{{ filesize_testlink }}"
size: "500 KiB"
register: filesize_test_symlink_04
- name: Assert that results are as expected
assert:
that:
- filesize_test_symlink_01 is changed
- filesize_test_symlink_02 is changed
- filesize_test_symlink_03 is not changed
- filesize_test_symlink_04 is not changed
- filesize_test_symlink_01.state is undefined
- filesize_test_symlink_02.state in ["file"]
- filesize_test_symlink_01.size is undefined
- filesize_test_symlink_02.size == 512000
- filesize_test_symlink_03.size == 512000
- filesize_test_symlink_04.size == 512000
- filesize_test_symlink_04.path != filesize_testlink
- name: Remove test file
file:
path: "{{ filesize_testfile }}"
state: absent
- name: Remove test link
file:
path: "{{ filesize_testlink }}"
state: absent