2016-09-09 20:36:23 +03:00
|
|
|
# Copyright 2012 OpenStack Foundation
|
|
|
|
# All Rights Reserved.
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
|
|
# not use this file except in compliance with the License. You may obtain
|
|
|
|
# a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
# License for the specific language governing permissions and limitations
|
|
|
|
# under the License.
|
|
|
|
|
|
|
|
from __future__ import print_function
|
|
|
|
|
|
|
|
import errno
|
|
|
|
import hashlib
|
|
|
|
import os
|
|
|
|
import six
|
|
|
|
import sys
|
|
|
|
|
|
|
|
if os.name == 'nt':
|
|
|
|
import msvcrt
|
|
|
|
else:
|
|
|
|
msvcrt = None
|
|
|
|
|
|
|
|
from oslo_utils import encodeutils
|
|
|
|
from oslo_utils import importutils
|
|
|
|
|
|
|
|
SENSITIVE_HEADERS = ('X-Auth-Token', )
|
|
|
|
|
|
|
|
|
|
|
|
def env(*vars, **kwargs):
|
|
|
|
"""Search for the first defined of possibly many env vars.
|
|
|
|
|
|
|
|
Returns the first environment variable defined in vars, or
|
|
|
|
returns the default defined in kwargs.
|
|
|
|
"""
|
|
|
|
for v in vars:
|
|
|
|
value = os.environ.get(v, None)
|
|
|
|
if value:
|
|
|
|
return value
|
|
|
|
return kwargs.get('default', '')
|
|
|
|
|
|
|
|
|
|
|
|
def import_versioned_module(version, submodule=None):
|
|
|
|
module = 'glareclient.v%s' % version
|
|
|
|
if submodule:
|
|
|
|
module = '.'.join((module, submodule))
|
|
|
|
return importutils.import_module(module)
|
|
|
|
|
|
|
|
|
|
|
|
def exit(msg='', exit_code=1):
|
|
|
|
if msg:
|
2016-09-28 18:06:46 +03:00
|
|
|
print(encodeutils.safe_decode(msg), file=sys.stderr)
|
2016-09-09 20:36:23 +03:00
|
|
|
sys.exit(exit_code)
|
|
|
|
|
|
|
|
|
|
|
|
def integrity_iter(iter, checksum):
|
|
|
|
"""Check blob integrity.
|
|
|
|
|
|
|
|
:raises: IOError
|
|
|
|
"""
|
|
|
|
md5sum = hashlib.md5()
|
|
|
|
for chunk in iter:
|
|
|
|
yield chunk
|
|
|
|
if isinstance(chunk, six.string_types):
|
|
|
|
chunk = six.b(chunk)
|
|
|
|
md5sum.update(chunk)
|
|
|
|
md5sum = md5sum.hexdigest()
|
|
|
|
if md5sum != checksum:
|
|
|
|
raise IOError(errno.EPIPE,
|
|
|
|
'Corrupt blob download. Checksum was %s expected %s' %
|
|
|
|
(md5sum, checksum))
|
|
|
|
|
|
|
|
|
|
|
|
class IterableWithLength(object):
|
|
|
|
def __init__(self, iterable, length):
|
|
|
|
self.iterable = iterable
|
|
|
|
self.length = length
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
try:
|
|
|
|
for chunk in self.iterable:
|
|
|
|
yield chunk
|
|
|
|
finally:
|
|
|
|
self.iterable.close()
|
|
|
|
|
|
|
|
def next(self):
|
|
|
|
return next(self.iterable)
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
return self.length
|
|
|
|
|
|
|
|
|
|
|
|
def get_item_properties(item, fields, mixed_case_fields=None, formatters=None):
|
|
|
|
"""Return a tuple containing the item properties.
|
|
|
|
|
|
|
|
:param item: a single item resource (e.g. Server, Project, etc)
|
|
|
|
:param fields: tuple of strings with the desired field names
|
|
|
|
:param mixed_case_fields: tuple of field names to preserve case
|
|
|
|
:param formatters: dictionary mapping field names to callables
|
|
|
|
to format the values
|
|
|
|
"""
|
|
|
|
if mixed_case_fields is None:
|
|
|
|
mixed_case_fields = []
|
|
|
|
if formatters is None:
|
|
|
|
formatters = {}
|
|
|
|
|
|
|
|
row = []
|
|
|
|
|
|
|
|
for field in fields:
|
|
|
|
if field in mixed_case_fields:
|
|
|
|
field_name = field.replace(' ', '_')
|
|
|
|
else:
|
|
|
|
field_name = field.lower().replace(' ', '_')
|
|
|
|
data = item[field_name]
|
|
|
|
if field in formatters:
|
|
|
|
row.append(formatters[field](data))
|
|
|
|
else:
|
|
|
|
row.append(data)
|
|
|
|
return tuple(row)
|
|
|
|
|
|
|
|
|
|
|
|
def make_size_human_readable(size):
|
|
|
|
suffix = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB']
|
|
|
|
base = 1024.0
|
|
|
|
index = 0
|
|
|
|
|
|
|
|
if size is None:
|
|
|
|
size = 0
|
|
|
|
while size >= base:
|
|
|
|
index = index + 1
|
|
|
|
size = size / base
|
|
|
|
|
|
|
|
padded = '%.1f' % size
|
|
|
|
stripped = padded.rstrip('0').rstrip('.')
|
|
|
|
|
|
|
|
return '%s%s' % (stripped, suffix[index])
|
|
|
|
|
|
|
|
|
|
|
|
def save_blob(data, path):
|
|
|
|
"""Save a blob to the specified path.
|
|
|
|
|
|
|
|
:param data: blob of the artifact
|
|
|
|
:param path: path to save the blob to
|
|
|
|
"""
|
|
|
|
if path is None:
|
|
|
|
blob = getattr(sys.stdout, 'buffer',
|
|
|
|
sys.stdout)
|
|
|
|
else:
|
|
|
|
blob = open(path, 'wb')
|
|
|
|
try:
|
|
|
|
for chunk in data:
|
|
|
|
blob.write(chunk)
|
|
|
|
finally:
|
|
|
|
if path is not None:
|
|
|
|
blob.close()
|
|
|
|
|
|
|
|
|
|
|
|
def get_data_file(blob):
|
|
|
|
if blob:
|
|
|
|
return open(blob, 'rb')
|
|
|
|
else:
|
|
|
|
# distinguish cases where:
|
|
|
|
# (1) stdin is not valid (as in cron jobs):
|
|
|
|
# glare ... <&-
|
|
|
|
# (2) blob is provided through standard input:
|
|
|
|
# glare ... < /tmp/file or cat /tmp/file | glare ...
|
|
|
|
# (3) no blob provided:
|
|
|
|
# glare ...
|
|
|
|
try:
|
|
|
|
os.fstat(0)
|
|
|
|
except OSError:
|
|
|
|
# (1) stdin is not valid (closed...)
|
|
|
|
return None
|
|
|
|
if not sys.stdin.isatty():
|
|
|
|
# (2) blob data is provided through standard input
|
|
|
|
blob_data = sys.stdin
|
|
|
|
if hasattr(sys.stdin, 'buffer'):
|
|
|
|
blob_data = sys.stdin.buffer
|
|
|
|
if msvcrt:
|
|
|
|
msvcrt.setmode(blob_data.fileno(), os.O_BINARY)
|
|
|
|
return blob_data
|
|
|
|
else:
|
|
|
|
# (3) no blob data provided
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def get_file_size(file_obj):
|
|
|
|
"""Analyze file-like object and attempt to determine its size.
|
|
|
|
|
|
|
|
:param file_obj: file-like object.
|
|
|
|
:retval The file's size or None if it cannot be determined.
|
|
|
|
"""
|
|
|
|
if (hasattr(file_obj, 'seek') and hasattr(file_obj, 'tell') and
|
|
|
|
(six.PY2 or six.PY3 and file_obj.seekable())):
|
|
|
|
try:
|
|
|
|
curr = file_obj.tell()
|
|
|
|
file_obj.seek(0, os.SEEK_END)
|
|
|
|
size = file_obj.tell()
|
|
|
|
file_obj.seek(curr)
|
|
|
|
return size
|
|
|
|
except IOError as e:
|
|
|
|
if e.errno == errno.ESPIPE:
|
|
|
|
# Illegal seek. This means the file object
|
|
|
|
# is a pipe (e.g. the user is trying
|
|
|
|
# to pipe blob to the client,
|
|
|
|
# echo testdata | bin/glare add blah...), or
|
|
|
|
# that file object is empty, or that a file-like
|
|
|
|
# object which doesn't support 'seek/tell' has
|
|
|
|
# been supplied.
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
raise
|