Initial commit
add first helper: reclass-dump-params
diff --git a/reclass_tools/helpers/exec_result.py b/reclass_tools/helpers/exec_result.py
new file mode 100644
index 0000000..3dc6245
--- /dev/null
+++ b/reclass_tools/helpers/exec_result.py
@@ -0,0 +1,379 @@
+# Copyright 2016 Mirantis, Inc.
+#
+# 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 unicode_literals
+
+import json
+import threading
+
+import yaml
+
+from reclass_tools.helpers import proc_enums
+from reclass_tools import logger
+
+
+deprecated_aliases = {
+ 'stdout_str',
+ 'stderr_str',
+ 'stdout_json',
+ 'stdout_yaml'
+}
+
+
+class ExecResult(object):
+ __slots__ = [
+ '__cmd', '__stdout', '__stderr', '__exit_code',
+ '__stdout_str', '__stderr_str', '__stdout_brief', '__stderr_brief',
+ '__stdout_json', '__stdout_yaml',
+ '__lock'
+ ]
+
+ def __init__(self, cmd, stdout=None, stderr=None,
+ exit_code=proc_enums.ExitCodes.EX_INVALID):
+ """Command execution result read from fifo
+
+ :type cmd: str
+ :type stdout: list
+ :type stderr: list
+ :type exit_code: ExitCodes
+ """
+ self.__lock = threading.RLock()
+
+ self.__cmd = cmd
+ self.__stdout = stdout if stdout is not None else []
+ self.__stderr = stderr if stderr is not None else []
+
+ self.__exit_code = None
+ self.exit_code = exit_code
+
+ # By default is none:
+ self.__stdout_str = None
+ self.__stderr_str = None
+ self.__stdout_brief = None
+ self.__stderr_brief = None
+
+ self.__stdout_json = None
+ self.__stdout_yaml = None
+
+ @property
+ def lock(self):
+ """Lock object for thread-safe operation
+
+ :rtype: RLock
+ """
+ return self.__lock
+
+ @staticmethod
+ def _get_bytearray_from_array(src):
+ """Get bytearray from array of bytes blocks
+
+ :type src: list(bytes)
+ :rtype: bytearray
+ """
+ return bytearray(b''.join(src))
+
+ @staticmethod
+ def _get_str_from_bin(src):
+ """Join data in list to the string, with python 2&3 compatibility.
+
+ :type src: bytearray
+ :rtype: str
+ """
+ return src.strip().decode(
+ encoding='utf-8',
+ errors='backslashreplace'
+ )
+
+ @classmethod
+ def _get_brief(cls, data):
+ """Get brief output: 7 lines maximum (3 first + ... + 3 last)
+
+ :type data: list(bytes)
+ :rtype: str
+ """
+ src = data if len(data) <= 7 else data[:3] + [b'...\n'] + data[-3:]
+ return cls._get_str_from_bin(
+ cls._get_bytearray_from_array(src)
+ )
+
+ @property
+ def cmd(self):
+ """Executed command
+
+ :rtype: str
+ """
+ return self.__cmd
+
+ @property
+ def stdout(self):
+ """Stdout output as list of binaries
+
+ :rtype: list(bytes)
+ """
+ return self.__stdout
+
+ @stdout.setter
+ def stdout(self, new_val):
+ """Stdout output as list of binaries
+
+ :type new_val: list(bytes)
+ :raises: TypeError
+ """
+ if not isinstance(new_val, (list, type(None))):
+ raise TypeError('stdout should be list only!')
+ with self.lock:
+ self.__stdout_str = None
+ self.__stdout_brief = None
+ self.__stdout_json = None
+ self.__stdout_yaml = None
+ self.__stdout = new_val
+
+ @property
+ def stderr(self):
+ """Stderr output as list of binaries
+
+ :rtype: list(bytes)
+ """
+ return self.__stderr
+
+ @stderr.setter
+ def stderr(self, new_val):
+ """Stderr output as list of binaries
+
+ :type new_val: list(bytes)
+ :raises: TypeError
+ """
+ if not isinstance(new_val, (list, None)):
+ raise TypeError('stderr should be list only!')
+ with self.lock:
+ self.__stderr_str = None
+ self.__stderr_brief = None
+ self.__stderr = new_val
+
+ @property
+ def stdout_bin(self):
+ """Stdout in binary format
+
+ Sometimes logging is used to log binary objects too (example: Session),
+ and for debug purposes we can use this as data source.
+ :rtype: bytearray
+ """
+ with self.lock:
+ return self._get_bytearray_from_array(self.stdout)
+
+ @property
+ def stderr_bin(self):
+ """Stderr in binary format
+
+ :rtype: bytearray
+ """
+ with self.lock:
+ return self._get_bytearray_from_array(self.stderr)
+
+ @property
+ def stdout_str(self):
+ """Stdout output as string
+
+ :rtype: str
+ """
+ with self.lock:
+ if self.__stdout_str is None:
+ self.__stdout_str = self._get_str_from_bin(self.stdout_bin)
+ return self.__stdout_str
+
+ @property
+ def stderr_str(self):
+ """Stderr output as string
+
+ :rtype: str
+ """
+ with self.lock:
+ if self.__stderr_str is None:
+ self.__stderr_str = self._get_str_from_bin(self.stderr_bin)
+ return self.__stderr_str
+
+ @property
+ def stdout_brief(self):
+ """Brief stdout output (mostly for exceptions)
+
+ :rtype: str
+ """
+ with self.lock:
+ if self.__stdout_brief is None:
+ self.__stdout_brief = self._get_brief(self.stdout)
+ return self.__stdout_brief
+
+ @property
+ def stderr_brief(self):
+ """Brief stderr output (mostly for exceptions)
+
+ :rtype: str
+ """
+ with self.lock:
+ if self.__stderr_brief is None:
+ self.__stderr_brief = self._get_brief(self.stderr)
+ return self.__stderr_brief
+
+ @property
+ def exit_code(self):
+ """Return(exit) code of command
+
+ :rtype: int
+ """
+ return self.__exit_code
+
+ @exit_code.setter
+ def exit_code(self, new_val):
+ """Return(exit) code of command
+
+ :type new_val: int
+ """
+ if not isinstance(new_val, (int, proc_enums.ExitCodes)):
+ raise TypeError('Exit code is strictly int')
+ with self.lock:
+ if isinstance(new_val, int) and \
+ new_val in proc_enums.ExitCodes.__members__.values():
+ new_val = proc_enums.ExitCodes(new_val)
+ self.__exit_code = new_val
+
+ def __deserialize(self, fmt):
+ """Deserialize stdout as data format
+
+ :type fmt: str
+ :rtype: object
+ :raises: DevopsError
+ """
+ try:
+ if fmt == 'json':
+ return json.loads(self.stdout_str, encoding='utf-8')
+ elif fmt == 'yaml':
+ return yaml.safe_load(self.stdout_str)
+ except BaseException:
+ tmpl = (
+ " stdout is not valid {fmt}:\n"
+ '{{stdout!r}}\n'.format(
+ fmt=fmt))
+ logger.exception(self.cmd + tmpl.format(stdout=self.stdout_str))
+ raise TypeError(
+ self.cmd + tmpl.format(stdout=self.stdout_brief))
+ msg = '{fmt} deserialize target is not implemented'.format(fmt=fmt)
+ logger.error(msg)
+ raise NotImplementedError(msg)
+
+ @property
+ def stdout_json(self):
+ """JSON from stdout
+
+ :rtype: object
+ """
+ with self.lock:
+ if self.__stdout_json is None:
+ # noinspection PyTypeChecker
+ self.__stdout_json = self.__deserialize(fmt='json')
+ return self.__stdout_json
+
+ @property
+ def stdout_yaml(self):
+ """YAML from stdout
+
+ :rtype: Union(list, dict, None)
+ """
+ with self.lock:
+ if self.__stdout_yaml is None:
+ # noinspection PyTypeChecker
+ self.__stdout_yaml = self.__deserialize(fmt='yaml')
+ return self.__stdout_yaml
+
+ def __dir__(self):
+ return [
+ 'cmd', 'stdout', 'stderr', 'exit_code',
+ 'stdout_bin', 'stderr_bin',
+ 'stdout_str', 'stderr_str', 'stdout_brief', 'stderr_brief',
+ 'stdout_json', 'stdout_yaml',
+ 'lock'
+ ]
+
+ def __getitem__(self, item):
+ if item in dir(self):
+ return getattr(self, item)
+ raise IndexError(
+ '"{item}" not found in {dir}'.format(
+ item=item, dir=dir(self)
+ )
+ )
+
+ def __setitem__(self, key, value):
+ rw = ['stdout', 'stderr', 'exit_code']
+ if key in rw:
+ setattr(self, key, value)
+ return
+ if key in deprecated_aliases:
+ logger.warning(
+ '{key} is read-only and calculated automatically'.format(
+ key=key
+ )
+ )
+ return
+ if key in dir(self):
+ raise RuntimeError(
+ '{key} is read-only!'.format(key=key)
+ )
+ raise IndexError(
+ '{key} not found in {dir}'.format(
+ key=key, dir=rw
+ )
+ )
+
+ def __repr__(self):
+ return (
+ '{cls}(cmd={cmd!r}, stdout={stdout}, stderr={stderr}, '
+ 'exit_code={exit_code!s})'.format(
+ cls=self.__class__.__name__,
+ cmd=self.cmd,
+ stdout=self.stdout,
+ stderr=self.stderr,
+ exit_code=self.exit_code
+ ))
+
+ def __str__(self):
+ return (
+ "{cls}(\n\tcmd={cmd!r},"
+ "\n\t stdout=\n'{stdout_brief}',"
+ "\n\tstderr=\n'{stderr_brief}', "
+ '\n\texit_code={exit_code!s}\n)'.format(
+ cls=self.__class__.__name__,
+ cmd=self.cmd,
+ stdout_brief=self.stdout_brief,
+ stderr_brief=self.stderr_brief,
+ exit_code=self.exit_code
+ )
+ )
+
+ def __eq__(self, other):
+ return all(
+ (
+ getattr(self, val) == getattr(other, val)
+ for val in ['cmd', 'stdout', 'stderr', 'exit_code']
+ )
+ )
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __hash__(self):
+ return hash(
+ (
+ self.__class__, self.cmd, self.stdout_str, self.stderr_str,
+ self.exit_code
+ ))