Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new filter human_bytes: convert a string (ex: 1Mo, 1K) into bytes #12074

Merged
merged 2 commits into from
Aug 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
116 changes: 72 additions & 44 deletions lib/ansible/module_utils/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
BOOLEANS_FALSE = ['n', 'no', 'off', '0', 'false', 0, False]
BOOLEANS = BOOLEANS_TRUE + BOOLEANS_FALSE

SIZE_RANGES = { 'Y': 1<<80, 'Z': 1<<70, 'E': 1<<60, 'P': 1<<50, 'T': 1<<40, 'G': 1<<30, 'M': 1<<20, 'K': 1<<10, 'B': 1 }

# ansible modules can be written in any language. To simplify
# development of Python modules, the functions available here can
# be used to do many common tasks
Expand Down Expand Up @@ -466,6 +468,72 @@ def heuristic_log_sanitize(data, no_log_values=None):
output = remove_values(output, no_log_values)
return output

def bytes_to_human(size, isbits=False, unit=None):

base = 'Bytes'
if isbits:
base = 'bits'
suffix = ''

for suffix, limit in sorted(iteritems(SIZE_RANGES), key=lambda item: -item[1]):
if (unit is None and size >= limit) or unit is not None and unit.upper() == suffix[0]:
break

if limit != 1:
suffix += base[0]
else:
suffix = base

return '%.2f %s' % (float(size)/ limit, suffix)

def human_to_bytes(number, default_unit=None, isbits=False):

'''
Convert number in string format into bytes (ex: '2K' => 2048) or using unit argument
ex:
human_to_bytes('10M') <=> human_to_bytes(10, 'M')
'''
m = re.search('^\s*(\d*\.?\d*)\s*([A-Za-z]+)?', str(number), flags=re.IGNORECASE)
if m is None:
raise ValueError("human_to_bytes() can't interpret following string: %s" % str(number))
try:
num = float(m.group(1))
except:
raise ValueError("human_to_bytes() can't interpret following number: %s (original input string: %s)" % (m.group(1), number))

unit = m.group(2)
if unit is None:
unit = default_unit

if unit is None:
''' No unit given, returning raw number '''
return int(round(num))
range_key = unit[0].upper()
try:
limit = SIZE_RANGES[range_key]
except:
raise ValueError("human_to_bytes() failed to convert %s (unit = %s). The suffix must be one of %s" % (number, unit, ", ".join(SIZE_RANGES.keys())))

# default value
unit_class = 'B'
unit_class_name = 'byte'
# handling bits case
if isbits:
unit_class = 'b'
unit_class_name = 'bit'
# check unit value if more than one character (KB, MB)
if len(unit) > 1:
expect_message = 'expect %s%s or %s' % (range_key, unit_class, range_key)
if range_key == 'B':
expect_message = 'expect %s or %s' % (unit_class, unit_class_name)

if unit_class_name in unit.lower():
pass
elif unit[1] != unit_class:
raise ValueError("human_to_bytes() failed to convert %s. Value is not a valid string (%s)" % (number, expect_message))

return int(round(num * limit))

def is_executable(path):
'''is the given path executable?

Expand Down Expand Up @@ -1468,7 +1536,7 @@ def _check_type_bytes(self, value):

def _check_type_bits(self, value):
try:
self.human_to_bytes(value, bits=True)
self.human_to_bytes(value, isbits=True)
except ValueError:
raise TypeError('%s cannot be converted to a Bit value' % type(value))

Expand Down Expand Up @@ -2181,53 +2249,13 @@ def append_to_file(self, filename, str):
fh.close()

def bytes_to_human(self, size):

ranges = (
(1 << 70, 'ZB'),
(1 << 60, 'EB'),
(1 << 50, 'PB'),
(1 << 40, 'TB'),
(1 << 30, 'GB'),
(1 << 20, 'MB'),
(1 << 10, 'KB'),
(1, 'Bytes')
)
for limit, suffix in ranges:
if size >= limit:
break
return '%.2f %s' % (float(size)/ limit, suffix)
return bytes_to_human(size)

# for backwards compatibility
pretty_bytes = bytes_to_human

def human_to_bytes(number, bits=False):

result = None
suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB']
full = 'Bytes'

if bits:
suffixes = [ x.replace('B', 'b') for x in suffixes ]
full = 'Bits'

if number is None:
result = 0
elif isinstance(number, int):
result = number
elif number.isdigit():
result = int(number)
elif full in number:
result = int(number.replace(full,''))
else:
for i, suffix in enumerate(suffixes):
if suffix in number:
result = int(number.replace(suffix ,'')) * (1024 ** i)
break

if result is None:
raise ValueError("Failed to convert %s. The suffix must be one of %s or %s" % (number, full, ', '.join(suffixes)))

return result
def human_to_bytes(self, number, isbits=False):
return human_to_bytes(number, isbits)

#
# Backwards compat
Expand Down
36 changes: 13 additions & 23 deletions lib/ansible/plugins/filter/mathstuff.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import math
import collections
from ansible import errors
from ansible.module_utils import basic

def unique(a):
if isinstance(a,collections.Hashable):
Expand Down Expand Up @@ -99,30 +100,18 @@ def inversepower(x, base=2):


def human_readable(size, isbits=False, unit=None):
''' Return a human readable string '''
try:
return basic.bytes_to_human(size, isbits, unit)
except:
raise errors.AnsibleFilterError("human_readable() can't interpret following string: %s" % size)

base = 'bits' if isbits else 'Bytes'
suffix = ''

ranges = (
(1<<70, 'Z'),
(1<<60, 'E'),
(1<<50, 'P'),
(1<<40, 'T'),
(1<<30, 'G'),
(1<<20, 'M'),
(1<<10, 'K'),
(1, base)
)

for limit, suffix in ranges:
if (unit is None and size >= limit) or \
unit is not None and unit.upper() == suffix:
break

if limit != 1:
suffix += base[0]

return '%.2f %s' % (float(size)/ limit, suffix)
def human_to_bytes(size, default_unit=None, isbits=False):
''' Return bytes count from a human readable string '''
try:
return basic.human_to_bytes(size, default_unit, isbits)
except:
raise errors.AnsibleFilterError("human_to_bytes() can't interpret following string: %s" % size)

class FilterModule(object):
''' Ansible math jinja2 filters '''
Expand All @@ -147,5 +136,6 @@ def filters(self):

# computer theory
'human_readable' : human_readable,
'human_to_bytes' : human_to_bytes,

}
27 changes: 27 additions & 0 deletions test/integration/roles/test_filters/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,40 @@
- 'diff_result.stdout == ""'

- name: Verify human_readable
tags: "human_readable"
assert:
that:
- '"1.00 Bytes" == 1|human_readable'
- '"1.00 bits" == 1|human_readable(isbits=True)'
- '"10.00 KB" == 10240|human_readable'
- '"97.66 MB" == 102400000|human_readable'
- '"0.10 GB" == 102400000|human_readable(unit="G")'
- '"0.10 Gb" == 102400000|human_readable(isbits=True, unit="G")'

- name: Verify human_to_bytes
tags: "human_to_bytes"
assert:
that:
- "{{'0'|human_to_bytes}} == 0"
- "{{'0.1'|human_to_bytes}} == 0"
- "{{'0.9'|human_to_bytes}} == 1"
- "{{'1'|human_to_bytes}} == 1"
- "{{'10.00 KB'|human_to_bytes}} == 10240"
- "{{ '11 MB'|human_to_bytes}} == 11534336"
- "{{ '1.1 GB'|human_to_bytes}} == 1181116006"
- "{{'10.00 Kb'|human_to_bytes(isbits=True)}} == 10240"

- name: Verify human_to_bytes (bad string)
tags: "human_to_bytes"
set_fact: bad_string="{{'10.00 foo'|human_to_bytes}}"
ignore_errors: yes
register: _

- name: Verify human_to_bytes (bad string)
tags: "human_to_bytes"
assert:
that: "{{_.failed}}"

- name: Container lookups with extract
assert:
that:
Expand Down