summaryrefslogtreecommitdiffstats
path: root/lmi/scripts/common/command
diff options
context:
space:
mode:
authorMichal Minar <miminar@redhat.com>2014-04-22 15:19:56 +0200
committerMichal Minar <miminar@redhat.com>2014-04-24 09:58:44 +0200
commit2a78da4f98a583a382c553f2e1fc68299210e7d5 (patch)
tree53029ae0a8474a9ddc464419da7337d0d88dea13 /lmi/scripts/common/command
parent9138628a62461fd8914d5b82ec6a4ace964a4b2b (diff)
downloadopenlmi-scripts-2a78da4f98a583a382c553f2e1fc68299210e7d5.tar.gz
openlmi-scripts-2a78da4f98a583a382c553f2e1fc68299210e7d5.tar.xz
openlmi-scripts-2a78da4f98a583a382c553f2e1fc68299210e7d5.zip
moved lmi meta-command to openlmi-tools repository
Diffstat (limited to 'lmi/scripts/common/command')
-rw-r--r--lmi/scripts/common/command/__init__.py50
-rw-r--r--lmi/scripts/common/command/base.py341
-rw-r--r--lmi/scripts/common/command/checkresult.py140
-rw-r--r--lmi/scripts/common/command/endpoint.py367
-rw-r--r--lmi/scripts/common/command/helper.py137
-rw-r--r--lmi/scripts/common/command/lister.py176
-rw-r--r--lmi/scripts/common/command/meta.py839
-rw-r--r--lmi/scripts/common/command/multiplexer.py155
-rw-r--r--lmi/scripts/common/command/select.py195
-rw-r--r--lmi/scripts/common/command/session.py206
-rw-r--r--lmi/scripts/common/command/show.py112
-rw-r--r--lmi/scripts/common/command/util.py109
12 files changed, 0 insertions, 2827 deletions
diff --git a/lmi/scripts/common/command/__init__.py b/lmi/scripts/common/command/__init__.py
deleted file mode 100644
index a7c2df4..0000000
--- a/lmi/scripts/common/command/__init__.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright (C) 2013-2014 Red Hat, Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and documentation are
-# those of the authors and should not be interpreted as representing official
-# policies, either expressed or implied, of the FreeBSD Project.
-#
-# Authors: Michal Minar <miminar@redhat.com>
-#
-"""
-This subpackage defines base classes and utility functions for declaring
-commands. These serve as wrappers for functions in libraries specific to
-particular provider.
-
-Tree of these commands build a command line interface for this library.
-"""
-
-from lmi.scripts.common.command.base import LmiBaseCommand
-from lmi.scripts.common.command.checkresult import LmiCheckResult
-from lmi.scripts.common.command.endpoint import LmiEndPointCommand
-from lmi.scripts.common.command.lister import LmiInstanceLister
-from lmi.scripts.common.command.lister import LmiLister
-from lmi.scripts.common.command.multiplexer import LmiCommandMultiplexer
-from lmi.scripts.common.command.session import LmiSessionCommand
-from lmi.scripts.common.command.select import LmiSelectCommand
-from lmi.scripts.common.command.show import LmiShowInstance
-
-from lmi.scripts.common.command.helper import make_list_command
-from lmi.scripts.common.command.helper import register_subcommands
-from lmi.scripts.common.command.helper import select_command
diff --git a/lmi/scripts/common/command/base.py b/lmi/scripts/common/command/base.py
deleted file mode 100644
index cebf422..0000000
--- a/lmi/scripts/common/command/base.py
+++ /dev/null
@@ -1,341 +0,0 @@
-# Copyright (C) 2013-2014 Red Hat, Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and documentation are
-# those of the authors and should not be interpreted as representing official
-# policies, either expressed or implied, of the FreeBSD Project.
-#
-# Authors: Michal Minar <miminar@redhat.com>
-#
-"""
-Module defining base command class for all possible commands of ``lmi``
-meta-command.
-"""
-
-import abc
-import re
-
-# regular expression matching leading whitespaces not until the first line
-# containing non-white-spaces
-RE_LSPACES = re.compile(r'\A(\s*$.)*', re.DOTALL | re.MULTILINE)
-
-#: Default formatting options overriden by options passed onc ommand-line and
-#: set in configuration file.
-DEFAULT_FORMATTER_OPTIONS = {
- 'no_headings' : False,
- 'padding' : 0,
- 'human_friendly' : False,
-}
-
-class LmiBaseCommand(object):
- """
- Abstract base class for all commands handling command line arguments.
- Instances of this class are organized in a tree with root element being the
- ``lmi`` meta-command (if not running in interactive mode). Each such
- instance can have more child commands if its
- :py:meth:`LmiBaseCommand.is_multiplexer` method return ``True``. Each has
- one parent command except for the top level one, whose :py:attr:`parent`
- property returns ``None``.
-
- Set of commands is organized in a tree, where each command (except for the
- root) has its own parent. :py:meth:`is_end_point` method distinguishes
- leaves from nodes. The path from root command to the leaf is a sequence of
- commands passed to command line.
-
- There is also a special command called selector. Its :py:meth:`is_selector`
- method returns ``True``. It selects proper command that shall be passed all
- the arguments based on expression with profile requirements. It shares its
- name and parent with selected child.
-
- If the :py:meth:`LmiBaseCommand.has_own_usage` returns ``True``, the parent
- command won't process the whole command line and the remainder will be
- passed as a second argument to the :py:meth:`LmiBaseCommand.run` method.
-
- :param app: Main application object.
- :param string cmd_name: Name of command.
- :param parent: Parent command.
- :type parent: :py:class:`LmiBaseCommand`
- """
-
- __metaclass__ = abc.ABCMeta
-
- @classmethod
- def get_description(cls):
- """
- Return description for this command. This is usually a first line
- of documentation string of a class.
-
- :rtype: string
- """
- if cls.__doc__ is None:
- return ""
- return cls.__doc__.strip().split("\n", 1)[0]
-
- @classmethod
- def is_end_point(cls):
- """
- :returns: ``True``, if this command parses the rest of command line and
- can not have any child subcommands.
- :rtype: boolean
- """
- return True
-
- @classmethod
- def is_multiplexer(cls):
- """
- Is this command a multiplexer? Note that only one of
- :py:meth:`is_end_point`, :py:meth:`is_selector` and this method can
- evaluate to``True``.
-
- :returns: ``True`` if this command is not an end-point command and it's
- a multiplexer. It contains one or more subcommands. It consumes the
- first argument from command-line arguments and passes the rest to
- one of its subcommands.
- :rtype: boolean
- """
- return not cls.is_end_point()
-
- @classmethod
- def is_selector(cls):
- """
- Is this command a selector?
-
- :returns: ``True`` if this command is a subclass of
- :py:class:`lmi.scripts.common.command.select.LmiSelectCommand`.
- :rtype: boolean
- """
- return not cls.is_end_point() and not cls.is_multiplexer()
-
- @classmethod
- def has_own_usage(cls):
- """
- :returns: ``True``, if this command has its own usage string, which is
- returned by :py:meth:`LmiBaseCommand.get_description`. Otherwise
- the parent command must be queried.
- :rtype: boolean
- """
- return False
-
- @classmethod
- def child_commands(cls):
- """
- Abstract class method returning dictionary of child commands with
- structure: ::
-
- { "command-name" : cmd_factory, ... }
-
- Dictionary contains just a direct children (commands, which
- may immediately follow this particular command on command line).
- """
- raise NotImplementedError("child_commands() method must be overriden"
- " in a subclass")
-
- def __init__(self, app, cmd_name, parent=None):
- if not isinstance(cmd_name, basestring):
- raise TypeError('cmd_name must be a string')
- if parent is not None and not isinstance(parent, LmiBaseCommand):
- raise TypeError('parent must be an LmiBaseCommand instance')
- self._app = app
- self._cmd_name = cmd_name.strip()
- self._parent = parent
-
- @property
- def app(self):
- """ Return application object. """
- return self._app
-
- @property
- def parent(self):
- """ Return parent command. """
- return self._parent
-
- @property
- def cmd_name(self):
- """ Name of this subcommand as a single word. """
- return self._cmd_name
-
- @property
- def cmd_name_parts(self):
- """
- Convenience property calling :py:meth:`get_cmd_name_parts` to obtain
- command path as a list of all preceding command names.
-
- :rtype: list
- """
- return self.get_cmd_name_parts()
-
- @property
- def format_options(self):
- """
- Compose formatting options. Parent commands are queried for defaults. If
- command has no parent, default options will be taken from
- :py:attr:`DEFAULT_FORMATTER_OPTIONS` which are overriden by config
- settings.
-
- :returns: Arguments passed to formatter factory when formatter is
- for current command is constructed.
- :rtype: dictionary
- """
- if self.parent is None:
- options = DEFAULT_FORMATTER_OPTIONS.copy()
- options['no_headings'] = self.app.config.no_headings
- options['human_friendly'] = self.app.config.human_friendly
- else:
- options = self.parent.format_options
- return options
-
- @property
- def session(self):
- """
- :returns: Session object. Session for command and all of its children
- may be overriden with a call to :py:meth:`set_session_proxy`.
- :rtype: :py:class:`lmi.scripts.common.session.Session`
- """
- proxy = getattr(self, '_session_proxy', None)
- if proxy:
- return proxy
- if self.parent is not None:
- return self.parent.session
- return self.app.session
-
- def get_cmd_name_parts(self, all_parts=False, demand_own_usage=True,
- for_docopt=False):
- """
- Get name of this command as a list composed of names of all preceding
- commands since the top level one. When in interactive mode, only
- commands following the active one will be present.
-
- :param boolean full: Take no heed to the active command or interactive
- mode. Return all command names since top level node inclusive. This
- is overriden with *for_docopt* flag.
- :param boolean demand_own_usage: Wether to continue the upward
- traversal through command hieararchy past the active command until
- the command with its own usage is found. This is the default behaviour.
- :param boolean for_docopt: Docopt parser needs to be given arguments list
- without the first item compared to command names in usage string
- it receives. Thus this option causes skipping the first item that would
- be otherwise included.
- :returns: Command path. Returned list will always contain at least the
- name of this command.
- :rtype: list
- """
- parts = [self.cmd_name]
- cmd = self
- own_usage = cmd.has_own_usage()
- while ( cmd.parent is not None
- and (all_parts or self.app.active_command not in (cmd, cmd.parent))
- or (demand_own_usage and not own_usage)):
- cmd = cmd.parent
- parts.append(cmd.cmd_name)
- own_usage = own_usage or cmd.has_own_usage()
- if for_docopt and parts:
- parts.pop()
- return list(reversed(parts))
-
- def get_usage(self, proper=False):
- """
- Get command usage. Return value of this function is used by docopt
- parser as usage string. Command tree is traversed upwards until command
- with defined usage string is found. End point commands (leaves) require
- manually written usage, so the first command in the sequence of parents
- with own usage string is obtained and its usage returned. For nodes
- missing own usage string this can be generated based on its
- subcommands.
-
- :param boolean proper: Says, whether the usage string written
- manually is required or not. It applies only to node (not a leaf)
- commands without its own usage string.
- """
- if self.is_end_point() or self.has_own_usage() or proper:
- # get proper (manually written) usage, also referred as *own*
- cmd = self
- while not cmd.has_own_usage() and cmd.parent is not None:
- cmd = cmd.parent
- if cmd.__doc__ is None:
- docstr = "Usage: %s\n" % " ".join(self.cmd_name_parts)
- else:
- docstr = ( ( cmd.__doc__.rstrip()
- % {'cmd' : " ".join(cmd.cmd_name_parts)}
- ))
-
- match = RE_LSPACES.match(docstr)
- if match: # strip leading newlines
- docstr = docstr[match.end(0):]
-
- match = re.match(r'^ +', docstr)
- if match: # unindent help message
- re_lspaces = re.compile(r'^ {%s}' % match.end(0))
- docstr = "\n".join(re_lspaces.sub('', l)
- for l in docstr.splitlines())
- docstr += "\n"
-
- else:
- # generate usage string from what is known, applies to nodes
- # without own usage
- hlp = []
- if self.get_description():
- hlp.append(self.get_description())
- hlp.append("")
- hlp.append("Usage:")
- hlp.append(" %s <command> [<args> ...]"
- % " ".join(self.cmd_name_parts))
- hlp.append("")
- hlp.append("Commands:")
- cmd_max_len = max(len(c) for c in self.child_commands())
- for name, cmd in sorted(self.child_commands().items()):
- hlp.append((" %%-%ds %%s" % cmd_max_len)
- % (name, cmd.get_description()))
- docstr = "\n".join(hlp) + "\n"
-
- return docstr
-
- @abc.abstractmethod
- def run(self, args):
- """
- Handle the command line arguments. If this is not an end point
- command, it will pass the unhandled arguments to one of it's child
- commands. So the arguments are processed recursively by the instances
- of this class.
-
- :param list args: Arguments passed to the command line that were
- not yet parsed. It's the contents of ``sys.argv`` (if in
- non-interactive mode) from the current command on.
- :returns: Exit code of application. This maybe also be a boolean value
- or ``None``. ``None`` and ``True`` are treated as a success causing
- exit code to be 0.
- :rtype: integer
- """
- raise NotImplementedError("run method must be overriden in subclass")
-
- def set_session_proxy(self, session):
- """
- Allows to override session object. This is useful for especially for
- conditional commands (subclasses of
- :py:class:`~lmi.scripts.common.command.select.LmiSelectCommand`) that devide
- connections to groups satisfying particular expression. These groups
- are turned into session proxies containing just a subset of connections
- in global session object.
-
- :param session: Session object.
- """
- self._session_proxy = session
diff --git a/lmi/scripts/common/command/checkresult.py b/lmi/scripts/common/command/checkresult.py
deleted file mode 100644
index 27e9549..0000000
--- a/lmi/scripts/common/command/checkresult.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# Copyright (C) 2013-2014 Red Hat, Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and documentation are
-# those of the authors and should not be interpreted as representing official
-# policies, either expressed or implied, of the FreeBSD Project.
-#
-# Authors: Michal Minar <miminar@redhat.com>
-#
-"""
-This module defines LmiCheckResult command class and related utilities.
-"""
-
-import abc
-
-from lmi.scripts.common import Configuration
-from lmi.scripts.common import get_logger
-from lmi.scripts.common import formatter
-from lmi.scripts.common import errors
-from lmi.scripts.common.command import meta
-from lmi.scripts.common.command.session import LmiSessionCommand
-
-LOG = get_logger(__name__)
-
-class LmiResultFailed(errors.LmiFailed):
- """
- Exception raised when associated function returns unexpected result. This
- is evaluated by :py:meth:`LmiCheckResult.check_result` method.
- """
- pass
-
-def _make_result_failed(expected, result):
- """
- Instantiate :py:exc:`LmiResultFailed` exception with descriptive message
- composed of what was expected and what was returned instead.
-
- :rtype: :py:class:`LmiResultFailed`
- """
- return LmiResultFailed('failed (%s != %s)' % (repr(expected), repr(result)))
-
-class LmiCheckResult(LmiSessionCommand):
- """
- Run an associated action and check its result. It implicitly makes no
- output if the invocation is successful and expected result matches.
-
- List of additional recognized properties:
-
- ``EXPECT`` :
- Value, that is expected to be returned by invoked associated
- function. This can also be a callable taking two arguments:
-
- 1. options - Dictionary with parsed command line options
- returned by ``docopt``.
- 2. result - Return value of associated function.
-
- Using metaclass: :py:class:`~.meta.CheckResultMetaClass`.
- """
- __metaclass__ = meta.CheckResultMetaClass
-
- def __init__(self, *args, **kwargs):
- LmiSessionCommand.__init__(self, *args, **kwargs)
-
- def formatter_factory(self):
- return formatter.TableFormatter
-
- @abc.abstractmethod
- def check_result(self, options, result):
- """
- Check the returned value of associated function.
-
- :param dictionary options: Dictionary as returned by ``docopt`` parser
- after running
- :py:meth:`~.endpoint.LmiEndPointCommand.transform_options`.
- :param result: Any return value that will be compared against what is
- expected.
- :returns: Whether the result is expected value or not. If ``tuple``
- is returned, it contains ``(passed_flag, error_description)``.
- :rtype: boolean or tuple.
- """
- raise NotImplementedError("check_result must be overriden in subclass")
-
- def take_action(self, connection, args, kwargs):
- """
- Invoke associated method and check its return value for single host.
-
- :param list args: List of arguments to pass to the associated
- function.
- :param dictionary kwargs: Keyword arguments to pass to the associated
- function.
- :returns: Exit code (0 on success).
- :rtype: integer
- """
- try:
- result = self.execute_on_connection(connection, *args, **kwargs)
- passed = self.check_result(self._options, result)
- if isinstance(passed, tuple):
- if len(passed) != 2:
- raise errors.LmiUnexpectedResult('check_result() must'
- ' return either boolean or (passed_flag,'
- ' error_description), not "%s"' % repr(passed))
- if not passed[0]:
- raise LmiResultFailed(passed[1])
- elif not passed and hasattr(self.check_result, 'expected'):
- err = _make_result_failed(self.check_result.expected, result)
- raise err
- except LmiResultFailed:
- raise
- except Exception as err:
- LOG().debug("Failed to execute wrapped function.", exc_info=err)
- raise
- return 0
-
- def process_host_result(self, hostname, success, result):
- pass
-
- def process_session_results(self, session, results):
- if len(self.session) > 1:
- LOG().debug('Successful runs: %d\n',
- len([r for r in results.values() if r[0]]))
- LmiSessionCommand.process_session_results(self, session, results)
diff --git a/lmi/scripts/common/command/endpoint.py b/lmi/scripts/common/command/endpoint.py
deleted file mode 100644
index c744e34..0000000
--- a/lmi/scripts/common/command/endpoint.py
+++ /dev/null
@@ -1,367 +0,0 @@
-# Copyright (C) 2013-2014 Red Hat, Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and documentation are
-# those of the authors and should not be interpreted as representing official
-# policies, either expressed or implied, of the FreeBSD Project.
-#
-# Authors: Michal Minar <miminar@redhat.com>
-#
-"""
-Defines base command class for all endpoint commands. Those having no children.
-"""
-import abc
-import inspect
-import re
-from docopt import docopt
-
-from lmi.scripts.common import errors
-from lmi.scripts.common import formatter
-from lmi.scripts.common import get_logger
-from lmi.scripts.common.formatter import command as fcmd
-from lmi.scripts.common.command import base
-from lmi.scripts.common.command import meta
-from lmi.scripts.common.command import util
-
-LOG = get_logger(__name__)
-
-def opt_name_sanitize(opt_name):
- """
- Make a function parameter name out of option name. This replaces any
- character not suitable for python identificator with ``'_'`` and
- make the whole string lowercase.
-
- :param string opt_name: Option name.
- :returns: Modified option name.
- :rtype: string
- """
- return re.sub(r'[^a-zA-Z0-9]+', '_', opt_name).lower()
-
-def options_dict2kwargs(options):
- """
- Convert option name from resulting ``docopt`` dictionary to a valid python
- identificator token used as function argument name.
-
- :param dictionary options: Dictionary returned by docopt call.
- :returns: New dictionary with keys passable to function as argument
- names.
- :rtype: dictionary
- """
- # (new_name, value) for each pair in options dictionary
- kwargs = {}
- # (new_name, name)
- orig_names = {}
- for name, value in options.items():
- for (reg, func) in (
- (util.RE_OPT_BRACKET_ARGUMENT, lambda m: m.group('name')),
- (util.RE_OPT_UPPER_ARGUMENT, lambda m: m.group('name')),
- (util.RE_OPT_SHORT_OPTION, lambda m: m.group(0)),
- (util.RE_OPT_LONG_OPTION, lambda m: m.group(0)),
- (util.RE_COMMAND_NAME, lambda m: m.group(0))):
- match = reg.match(name)
- if match:
- new_name = func(match)
- break
- else:
- raise errors.LmiError(
- 'Failed to convert argument "%s" to function option.' %
- name)
- if new_name == '--':
- continue # ignore double dash
- new_name = opt_name_sanitize(new_name)
- if new_name in kwargs:
- raise errors.LmiError('Option clash for "%s" and "%s", which both'
- ' translate to "%s".' % (name, orig_names[new_name], new_name))
- kwargs[new_name] = value
- orig_names[new_name] = name
- return kwargs
-
-class LmiEndPointCommand(base.LmiBaseCommand):
- """
- Base class for any leaf command.
-
- List of additional recognized properties:
-
- ``CALLABLE`` : ``tuple``
- Associated function. Will be wrapped in
- :py:meth:`LmiEndPointCommand.execute` method and will be accessible
- directly as a ``cmd.execute.dest`` property. It may be specified
- either as a string in form ``"<module_name>:<callable>"`` or as a
- reference to callable itself.
- ``ARG_ARRAY_SUFFIX`` : ``str``
- String appended to every option parsed by ``docopt`` having list as
- an associated value. It defaults to empty string. This modification
- is applied before calling
- :py:meth:`LmiEndPointCommand.verify_options` and
- :py:meth:`LmiEndPointCommand.transform_options`.
- ``FORMATTER`` : callable
- Default formatter factory for instances of given command. This
- factory accepts an output stream as the only parameter and returns
- an instance of :py:class:`~lmi.scripts.common.formatter.Formatter`.
-
- Using metaclass:
- :py:class:`.meta.EndPointCommandMetaClass`.
- """
- __metaclass__ = meta.EndPointCommandMetaClass
-
- def __init__(self, *args, **kwargs):
- super(LmiEndPointCommand, self).__init__(*args, **kwargs)
- self._formatter = None
- # saved options dictionary after call to transform_options()
- self._options = None
-
- @abc.abstractmethod
- def execute(self, *args, **kwargs):
- """
- Subclasses must override this method to pass given arguments to
- command library function. This function shall be specified in
- ``CALLABLE`` property.
- """
- raise NotImplementedError("execute method must be overriden"
- " in subclass")
-
- def formatter_factory(self):
- """
- Subclasses shall override this method to provide default formatter
- factory for printing output.
-
- :returns: Subclass of basic formatter.
- """
- return formatter.Formatter
-
- @classmethod
- def dest_pos_args_count(cls):
- """
- Number of positional arguments the associated function takes from
- command. These arguments are created by the command alone -- they do
- not belong to options in usage string. Function can take additional
- positional arguments that need to be covered by usage string.
-
- :rtype: integer
- """
- dest = getattr(cls.execute, "dest", cls.execute)
- abstract = dest == cls.execute and util.is_abstract_method(
- cls, 'execute', True)
- # if the destination function is not yet defined (abstract is True)
- # let's assume it's not a method => 0 positional arguments needed
- return 1 if not abstract and inspect.ismethod(dest) else 0
-
- def run_with_args(self, args, kwargs):
- """
- Process end-point arguments and exit.
-
- :param list args: Positional arguments to pass to associated
- function in command library.
- :param dictionary kwargs: Keyword arguments as a dictionary.
- :returns: Exit code of application.
- :rtype: integer
- """
- return self.execute(*args, **kwargs)
-
- @property
- def formatter(self):
- """
- Return instance of default formatter.
-
- :rtype: :py:class:`~lmi.scripts.common.formatter.Formatter`
- """
- if self._formatter is None:
- opts = self.format_options
- factory = self.formatter_factory()
- argspec = inspect.getargspec(
- factory.__init__ if type(factory) is type else factory)
- if not argspec.keywords:
- kwargs = {k: v for k, v in opts.items() if k in argspec.args}
- self._formatter = factory(self.app.stdout, **kwargs)
- return self._formatter
-
- def _make_end_point_args(self, options):
- """
- Creates a pair of positional and keyword arguments for a call to
- associated function from command line options. All keyword
- options not expected by target function are removed.
-
- :param dictionary options: Output of ``docopt`` parser.
- :returns: Positional and keyword arguments as a pair.
- :rtype: tuple
- """
- # if execute method does not have a *dest* attribute, then it's
- # itself a destination
- dest = getattr(self.execute, "dest", self.execute)
- argspec = inspect.getargspec(dest)
- kwargs = options_dict2kwargs(options)
- # number of positional arguments not covered by usage string
- pos_args_count = self.dest_pos_args_count()
- to_remove = []
- # if associated function takes keyword arguments in a single
- # dictionary (kwargs), we can pass all options
- if argspec.keywords is None:
- # otherwise we need to remove any unhandled
- for opt_name in kwargs:
- if opt_name not in argspec.args[pos_args_count:]:
- if opt_name not in self.cmd_name_parts:
- LOG().debug('Option "%s" not handled in function "%s",'
- ' ignoring.', opt_name, self.cmd_name)
- to_remove.append(opt_name)
- for opt_name in to_remove:
- # remove options unhandled by function
- del kwargs[opt_name]
- args = []
- for arg_name in argspec.args[pos_args_count:]:
- if arg_name not in kwargs:
- raise errors.LmiCommandError(
- self.__module__, self.__class__.__name__,
- 'registered command "%s" expects option "%s", which'
- ' is not covered in usage string'
- % (self.cmd_name, arg_name))
- args.append(kwargs.pop(arg_name))
- return args, kwargs
-
- def _preprocess_options(self, options):
- """
- This method may be overriden by
- :py:class:`~.meta.EndPointCommandMetaClass`
- as a result of processing ``ARG_ARRAY_SUFFIX`` and other properties
- modifying names of parsed options.
-
- This should not be overriden in command class's body.
-
- :param dictionary options: The result of ``docopt`` parser invocation
- which can be modified by this method.
- """
- pass
-
- def _parse_args(self, args):
- """
- Run ``docopt`` command line parser on given list of arguments.
- Removes all unrelated commands from created dictionary of options.
-
- :param list args: List of command line arguments just after the
- current command.
- :returns: Dictionary with parsed options. Please refer to
- docopt_ documentation for more informations.
- :rtype: dictionary
-
- .. _docopt: http://docopt.org/
- """
- full_args = self.get_cmd_name_parts(for_docopt=True) + args
- options = docopt(self.get_usage(), full_args, help=False)
- self._preprocess_options(options)
-
- # remove all command names from options
- cmd = self.parent
- while cmd is not None and not cmd.has_own_usage():
- cmd = cmd.parent
- if cmd is not None:
- for scn in cmd.child_commands():
- try:
- del options[scn]
- except KeyError:
- LOG().warn('Usage string of "%s.%s" command does not'
- ' contain registered command "%s" command.',
- cmd.__module__, cmd.__class__.__name__, scn)
- # remove also the root command name from options
- if cmd is not None and cmd.cmd_name in options:
- del options[cmd.cmd_name]
- return options
-
- def verify_options(self, options):
- """
- This method can be overriden in subclasses to check, whether the
- options given on command line are valid. If any flaw is discovered, an
- :py:exc:`~lmi.scripts.common.errors.LmiInvalidOptions` exception shall
- be raised. Any returned value is ignored.
-
- .. note::
- This is run before :py:meth:`transform_options()` method.
-
- :param dictionary options: Dictionary as returned by ``docopt`` parser.
- """
- pass
-
- def transform_options(self, options):
- """
- This method can be overriden in subclasses if options shall be somehow
- modified before passing them associated function.
-
- .. note::
- Run after :py:meth:`verify_options()` method.
-
- :param dictionary options: Dictionary as returned by ``docopt`` parser.
- """
- pass
-
- def produce_output(self, data):
- """
- This method can be use to render and print results with default
- formatter.
-
- :param data: Is an object expected by the
- :py:meth:`~lmi.scripts.common.formatter.Formatter.produce_output`
- method of formatter.
- """
- self.formatter.produce_output(data)
-
- def run(self, args):
- """
- Create options dictionary from input arguments, verify them,
- transform them, make positional and keyword arguments out of them and
- pass them to ``process_session()``.
-
- :param list args: List of command arguments.
- :returns: Exit code of application.
- :rtype: integer
- """
- options = self._parse_args(args)
- self.verify_options(options)
- self.transform_options(options)
- self._options = options.copy()
- args, kwargs = self._make_end_point_args(options)
- return self.run_with_args(args, kwargs)
-
- def _print_errors(self, error_list, new_line=True):
- """
- Print list of errors.
-
- :param list errors: Errors to print. Each error is a ``tuple``: ::
-
- (hostname, [error, error])
-
- Where ``error`` may be a test description or an instance of
- exception.
- :param new_line: Whether to print the new line before new error
- table is printed.
- """
- fmt = formatter.ErrorFormatter(self.app.stderr)
- if new_line:
- fmt.out.write('\n')
- if error_list:
- new_table_cmd = fcmd.NewTableCommand("There %s %d error%s" %
- ( 'were' if len(error_list) > 1 else 'was'
- , len(error_list)
- , 's' if len(error_list) > 1 else ''))
- fmt.produce_output((new_table_cmd, ))
- for hostname, host_errors in error_list:
- fmt.produce_output((fcmd.NewHostCommand(hostname), ))
- fmt.produce_output(host_errors)
-
diff --git a/lmi/scripts/common/command/helper.py b/lmi/scripts/common/command/helper.py
deleted file mode 100644
index 578fa8b..0000000
--- a/lmi/scripts/common/command/helper.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# Copyright (C) 2013-2014 Red Hat, Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and documentation are
-# those of the authors and should not be interpreted as representing official
-# policies, either expressed or implied, of the FreeBSD Project.
-#
-# Authors: Michal Minar <miminar@redhat.com>
-#
-"""
-Module with convenient function for defining user commands.
-"""
-
-from lmi.scripts.common.command import LmiLister
-from lmi.scripts.common.command import LmiCommandMultiplexer
-from lmi.scripts.common.command import LmiSelectCommand
-from lmi.scripts.common.command import util
-
-def make_list_command(func,
- name=None,
- columns=None,
- verify_func=None,
- transform_func=None):
- """
- Create a command subclassed from :py:class:`~.lister.LmiLister`. Please
- refer to this class for detailed usage.
-
- :param func: Contents of ``CALLABLE`` property.
- :type func: string or callable
- :param string name: Optional name of resulting class. If not given,
- it will be made from the name of associated function.
- :param tuple columns: Contents of ``COLUMNS`` property.
- :param callable verify_func: Callable overriding
- py:meth:`~.endpoint.LmiEndPointCommand.verify_options` method.
- :param callable transform_func: Callable overriding
- :py:meth:`~.endpoint.LmiEndPointCommand.transform_options` method.
- :returns: Subclass of :py:class:`~.lister.LmiLister`.
- :rtype: type
- """
- if name is None:
- if isinstance(func, basestring):
- name = func.split('.')[-1]
- else:
- name = func.__name__
- if not name.startswith('_'):
- name = '_' + name.capitalize()
- props = { 'COLUMNS' : columns
- , 'CALLABLE' : func
- , '__module__' : util.get_module_name() }
- if verify_func:
- props['verify_options'] = verify_func
- if transform_func:
- props['transform_options'] = transform_func
- return LmiLister.__metaclass__(name, (LmiLister, ), props)
-
-def register_subcommands(command_name, usage, command_map,
- fallback_command=None):
- """
- Create a multiplexer command (a node in a tree of commands).
-
- :param string command_name: Name of created command. The same as will
- be given on a command line.
- :param string usage: Usage string parseable by ``docopt``.
- :param dictionary command_map: Dictionary of subcommands. Associates
- command names to their factories. It's assigned to ``COMMANDS``
- property.
- :param fallback_command: Command factory used when no command is given
- on command line.
- :type fallback_command: :py:class:`~.endpoint.LmiEndPointCommand`
- :returns: Subclass of :py:class:`~.multiplexer.LmiCommandMultiplexer`.
- :rtype: type
- """
- props = { 'COMMANDS' : command_map
- , 'OWN_USAGE' : True
- , '__doc__' : usage
- , '__module__' : util.get_module_name()
- , 'FALLBACK_COMMAND' : fallback_command }
- return LmiCommandMultiplexer.__metaclass__(command_name,
- (LmiCommandMultiplexer, ), props)
-
-def select_command(command_name, *args, **kwargs):
- """
- Create command selector that loads command whose requirements are met.
-
- Example of invocation: ::
-
- Hardware = select_command('Hardware',
- ("Openlmi-Hardware >= 0.4.2", "lmi.scripts.hardware.current.Cmd"),
- ("Openlmi-Hardware < 0.4.2" , "lmi.scripts.hardware.pre042.Cmd"),
- default=HwMissing
- )
-
- Above example checks remote broker for OpenLMI-Hardware provider. If it is
- installed and its version is equal or higher than 0.4.2, command from
- ``current`` module will be used. For older registered versions command
- contained in ``pre042`` module will be loaded. If hardware provider is not
- available, HwMissing command will be loaded instead.
-
- .. seealso::
- Check out the grammer describing language used in these conditions at
- :py:mod:`lmi.scripts.common.versioncheck.parser`.
-
- :param args: List of pairs ``(condition, command)`` that are inspected in
- given order until single condition is satisfied. Associated command is
- then loaded. Command is either a reference to command class or path to
- it given as string. In latter case last dot divides module's import
- path and command name.
- :param default: This command will be loaded when no condition from *args*
- is satisfied.
- """
- props = { 'SELECT' : args
- , 'DEFAULT' : kwargs.get('default', None)
- , '__module__' : util.get_module_name()
- }
- return LmiSelectCommand.__metaclass__(command_name,
- (LmiSelectCommand, ), props)
-
diff --git a/lmi/scripts/common/command/lister.py b/lmi/scripts/common/command/lister.py
deleted file mode 100644
index 6b36168..0000000
--- a/lmi/scripts/common/command/lister.py
+++ /dev/null
@@ -1,176 +0,0 @@
-# Copyright (C) 2013-2014 Red Hat, Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and documentation are
-# those of the authors and should not be interpreted as representing official
-# policies, either expressed or implied, of the FreeBSD Project.
-#
-# Authors: Michal Minar <miminar@redhat.com>
-#
-"""
-Defines command classes producing tablelike output.
-"""
-
-import abc
-from itertools import chain
-
-from lmi.scripts.common import errors
-from lmi.scripts.common import formatter
-from lmi.scripts.common import get_logger
-from lmi.scripts.common.command import meta
-from lmi.scripts.common.command.session import LmiSessionCommand
-from lmi.scripts.common.configuration import Configuration
-from lmi.scripts.common.formatter import command as fcmd
-
-LOG = get_logger(__name__)
-
-class LmiBaseListerCommand(LmiSessionCommand):
- """
- Base class for all lister commands.
- """
-
- @classmethod
- def get_columns(cls):
- """
- :returns: Column names for resulting table. ``COLUMNS`` property
- will be converted to this class method. If ``None``, the associated
- function shall return column names as the first tuple of returned
- list. If empty tuple or list, no header shall be printed and associated
- function returns just data rows.
- :rtype: list or tuple or None
- """
- return None
-
- def formatter_factory(self):
- if self.app.config.lister_format == Configuration.LISTER_FORMAT_CSV:
- return formatter.CsvFormatter
- else:
- return formatter.TableFormatter
-
-class LmiLister(LmiBaseListerCommand):
- """
- End point command outputting a table for each host. Associated function
- shall return a list of rows. Each row is represented as a tuple holding
- column values.
-
- List of additional recognized properties:
-
- ``COLUMNS`` : ``tuple``
- Column names. It's a tuple with name for each column. Each row
- shall then contain the same number of items as this tuple. If
- omitted, associated function is expected to provide them in the
- first row of returned list. It's translated to ``get_columns()``
- class method.
-
- Using metaclass: :py:class:`~.meta.ListerMetaClass`.
- """
- __metaclass__ = meta.ListerMetaClass
-
- def take_action(self, connection, args, kwargs):
- """
- Collects results of single host.
-
- :param connection: Connection to a single host.
- :type connection: :py:class:`lmi.shell.LMIConnection`
- :param list args: Positional arguments for associated function.
- :param dictionary kwargs: Keyword arguments for associated function.
- :returns: Column names and item list as a pair.
- :rtype: tuple
- """
- res = self.execute_on_connection(connection, *args, **kwargs)
- columns = self.get_columns()
- if isinstance(columns, (tuple, list)) and columns:
- command = fcmd.NewTableHeaderCommand(columns)
- res = chain((command, ), res)
- elif columns is None:
- resi = iter(res)
- command = fcmd.NewTableHeaderCommand(resi.next())
- res = chain((command, ), resi)
- return res
-
-class LmiInstanceLister(LmiBaseListerCommand):
- """
- End point command outputting a table of instances for each host.
- Associated function shall return a list of instances. They may be
- prepended with column names depending on value of ``DYNAMIC_PROPERTIES``.
- Each instance will occupy single row of table with property values being a
- content of cells.
-
- List of additional recognized properties is the same as for
- :py:class:`~.show.LmiShowInstance`. There is just one difference. Either
- ``DYNAMIC_PROPERTIES`` must be ``True`` or ``PROPERTIES`` must be filled.
-
- Using metaclass: :py:class:`~.meta.InstanceListerMetaClass`.
- """
- __metaclass__ = meta.InstanceListerMetaClass
-
- @abc.abstractmethod
- def render(self, result):
- """
- This method can either be overriden in a subclass or left alone. In the
- latter case it will be generated by
- :py:class:`~.meta.InstanceListerMetaClass` metaclass with regard to
- ``PROPERTIES`` and ``DYNAMIC_PROPERTIES``.
-
- :param result: Either an instance to render or pair of properties and
- instance.
- :type result: :py:class:`lmi.shell.LMIInstance` or tuple
- :returns: List of pairs, where the first item is a label and second a
- value to render.
- :rtype: list
- """
- raise NotImplementedError(
- "render method must be overriden in subclass")
-
- def take_action(self, connection, args, kwargs):
- """
- Collects results of single host.
-
- :param connection: Connection to a single host.
- :type connection: :py:class:`lmi.shell.LMIConnection`
- :param list args: Positional arguments for associated function.
- :param dictionary kwargs: Keyword arguments for associated function.
- :returns: Column names and item list as a pair.
- :rtype: tuple
- """
- cols = self.get_columns()
- if cols is None:
- result = self.execute_on_connection(
- connection, *args, **kwargs)
- if not isinstance(result, tuple) or len(result) != 2:
- raise errors.LmiUnexpectedResult(
- self.__class__, "(properties, instances)", result)
- cols, data = result
- if not isinstance(cols, (tuple, list)):
- raise errors.LmiUnexpectedResult(
- self.__class__, "(tuple, ...)", (cols, '...'))
- header = [c if isinstance(c, basestring) else c[0] for c in cols]
- cmd = fcmd.NewTableHeaderCommand(columns=header)
- return chain((cmd, ), (self.render((cols, inst)) for inst in data))
- else:
- data = self.execute_on_connection(connection, *args, **kwargs)
- if not hasattr(data, '__iter__'):
- raise errors.LmiUnexpectedResult(
- self.__class__, 'list or generator', data)
- cmd = fcmd.NewTableHeaderCommand(columns=cols)
- return chain((cmd, ), (self.render(inst) for inst in data))
diff --git a/lmi/scripts/common/command/meta.py b/lmi/scripts/common/command/meta.py
deleted file mode 100644
index 7d8213e..0000000
--- a/lmi/scripts/common/command/meta.py
+++ /dev/null
@@ -1,839 +0,0 @@
-# Copyright (C) 2013-2014 Red Hat, Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and documentation are
-# those of the authors and should not be interpreted as representing official
-# policies, either expressed or implied, of the FreeBSD Project.
-#
-# Authors: Michal Minar <miminar@redhat.com>
-#
-"""
-Meta classes simplifying declaration of user commands.
-
-Each command is defined as a class with a set of properties. Some are
-mandatory, the others have some default values. Each of them is transformed by
-metaclasse to some function, class method or other property depending on
-command type and semantic of property. Property itself is removed from
-resulting class after being processed by meta class.
-"""
-
-import abc
-import inspect
-import re
-
-from lmi.scripts.common import Configuration
-from lmi.scripts.common import get_logger
-from lmi.scripts.common import errors
-from lmi.scripts.common.command import base
-from lmi.scripts.common.command import util
-from lmi.shell import LMIInstance
-from lmi.shell.LMIReturnValue import LMIReturnValue
-
-RE_CALLABLE = re.compile(
- r'^(?P<module>[a-z_]+(?:\.[a-z_]+)*):(?P<func>[a-z_]+)$',
- re.IGNORECASE)
-RE_ARRAY_SUFFIX = re.compile(r'^(?:[a-z_]+[a-z0-9_]*)?$', re.IGNORECASE)
-RE_OPTION = re.compile(r'^-+(?P<name>[^-+].*)$')
-RE_MODULE_PATH = re.compile(r'([a-zA-z_]\w+\.)+[a-zA-z_]\w+')
-
-FORMAT_OPTIONS = ('no_headings', 'human_friendly')
-
-LOG = get_logger(__name__)
-
-def _handle_usage(name, bases, dcl):
- """
- Take care of ``OWN_USAGE`` property. Supported values:
-
- `True`` :
- Means that documentation string of class is a usage string.
- ``False`` :
- No usage string for this command is defined.
- ``"usage string"`` :
- This property is a usage string.
-
- Defaults to ``False``.
-
- Usage string is an input parameter to ``docopt`` command-line options
- parser.
-
- :param string name: Name o command class.
- :param dictionary dcl: Class dictionary, which is modified by this
- function.
- """
- has_own_usage = False
- hlp = dcl.pop('OWN_USAGE', False)
- if hlp is True:
- if dcl['__doc__'] is None:
- raise errors.LmiCommandInvalidProperty(dcl['__module__'], name,
- "OWN_USAGE set to True, but no __doc__ string present!")
- has_own_usage = True
- elif isinstance(hlp, basestring):
- if not '__doc__' in dcl:
- dcl['__doc__'] = hlp
- else:
- if not 'get_usage' in dcl:
- def _new_get_usage(_self, proper=False):
- """ Get the usage string for ``doctopt`` parser. """
- return hlp
- dcl['get_usage'] = _new_get_usage
- has_own_usage = True
- elif ( dcl.get('__node__', None) is None
- and any(getattr(b, 'has_own_usage', lambda: False)() for b in bases)):
- # inherit doc string of base class
- dcl['__doc__'] = ( b.__doc__ for b in bases
- if getattr(b, 'has_own_usage', lambda: False)()).next()
- has_own_usage = True
- if has_own_usage:
- if not 'has_own_usage' in dcl:
- dcl['has_own_usage'] = classmethod(lambda _cls: True)
-
-def _make_execute_method(bases, dcl, func):
- """
- Creates ``execute()`` method of a new end point command.
-
- :param tuple bases: Base classes of new command.
- :param dictionary dcl: Class dictionary being modified by this method.
- :param callable func: A callable wrapped by this new command. It's usually
- being referred to as *associated function*. If ``None``, no function
- will be created -- ``dcl`` won't be modified.
- """
- if func is not None and util.is_abstract_method(
- bases, 'execute', missing_is_abstract=True):
- del dcl['CALLABLE']
- def _execute(__self__, __connection__, *args, **kwargs):
- """ Invokes associated function with given arguments. """
- return func(__connection__, *args, **kwargs)
- _execute.dest = func
- dcl['execute'] = _execute
-
-def _handle_namespace(dcl):
- """
- Overrides ``cim_namespace()`` class method if ``NAMESPACE`` property
- is given.
-
- :param dictionary dcl: Class dictionary being modified by this method.
- """
- if 'NAMESPACE' in dcl:
- namespace = dcl.pop('NAMESPACE')
- def _new_cim_namespace(_cls):
- """ Returns cim namespace used to modify connection object. """
- return namespace
- dcl['cim_namespace'] = classmethod(_new_cim_namespace)
-
-def _handle_callable(name, bases, dcl):
- """
- Process the ``CALLABLE`` property of end-point command. Create the
- ``execute()`` method based on it.
-
- :param string name: Name of command class to create.
- :param tuple bases: Base classes of new command.
- :param dictionary dcl: Class dictionary being modified by this method.
- """
- try:
- func = dcl.get('CALLABLE')
- if isinstance(func, basestring):
- match = RE_CALLABLE.match(func)
- if not match:
- raise errors.LmiCommandInvalidCallable(
- dcl['__module__'], name,
- 'Callable "%s" has invalid format (\':\' expected)'
- % func)
- mod_name = match.group('module')
- try:
- func = getattr(__import__(mod_name, globals(), locals(),
- [match.group('func')], 0),
- match.group('func'))
- except (ImportError, AttributeError):
- raise errors.LmiImportCallableFailed(
- dcl['__module__'], name, func)
- except KeyError:
- raise errors.LmiCommandMissingCallable(dcl['__module__'], name)
- if func is not None and not callable(func):
- raise errors.LmiCommandInvalidCallable(
- '"%s" is not a callable object or function.' % (
- func.__module__ + '.' + func.__name__))
-
- _make_execute_method(bases, dcl, func)
-
-def _make_render_all_properties(bases):
- """
- Creates ``render()`` method, rendering all properties of instance.
-
- :param tuple bases: Base classes of new command class.
- :returns: Rendering method taking CIM instance as an
- argument.
- :rtype: function
- """
- if util.is_abstract_method(bases, 'render', missing_is_abstract=True):
- def _render(_self, inst):
- """
- Return tuple of ``(column_names, values)`` ready for output by
- formatter.
- """
- column_names, values = [], []
- for prop_name, value in sorted(inst.properties_dict().iteritems()):
- column_names.append(prop_name)
- if value is None:
- value = ''
- values.append(value)
- return (column_names, values)
-
- return _render
-
-def _make_render_with_properties(properties, target_formatter_lister=False):
- """
- Creates ``render()`` method, rendering given instance properties.
-
- :param properties: (``list``) List of properties to render.
- :param target_formatter_lister: (``bool``) Whether the output is targeted
- for Show command or Lister. The former expects a pair of column_names
- and values. The latter expects just values.
- :rtype: (``function``) Rendering method taking CIM instance as an
- argument.
- """
- def _process_property(prop, inst):
- """
- Takes a single property and instance. Returns computed value.
-
- :rtype: ``(str, any)`` A pair of property name and value.
- """
- if isinstance(prop, basestring):
- prop_name = prop
- if not prop in inst.properties():
- LOG().warn('Property "%s" not present in instance of "%s".',
- prop, inst.path)
- value = "UNKNOWN"
- else:
- value = getattr(inst, prop)
- else:
- if not isinstance(prop, (tuple, list)):
- raise TypeError("prop must be a string or tuple, not %s" %
- repr(prop))
- prop_name = prop[0]
- try:
- if callable(prop[1]):
- value = prop[1](inst)
- else:
- value = getattr(inst, prop[1])
- except Exception as exc:
- LOG().exception('Failed to render property "%s": %s',
- prop[0], exc)
- value = "ERROR"
- if value is None:
- value = ''
- return prop_name, value
-
- if target_formatter_lister:
- def _render(self, inst):
- """
- Renders a limited set of properties and returns a row for instance
- table composed of property values.
- """
- if not isinstance(inst, LMIInstance):
- raise errors.LmiUnexpectedResult(
- self.__class__, 'LMIInstance object', inst)
- return tuple(_process_property(p, inst)[1] for p in properties)
-
- else:
- def _render(self, inst):
- """
- Renders a limited set of properties and returns a pair of
- column names and values.
- """
- if not isinstance(inst, LMIInstance):
- raise errors.LmiUnexpectedResult(
- self.__class__, 'LMIInstance object', inst)
- column_names, values = [], []
- for prop in properties:
- prop_name, value = _process_property(prop, inst)
- column_names.append(prop_name)
- values.append(value)
- return (column_names, values)
-
- return _render
-
-def _check_render_properties(name, dcl, props):
- """
- Make sanity check for ``PROPERTIES`` class property. Exception will be
- raised when any flaw discovered.
-
- :param string name: Name of class to be created.
- :param dictionary dcl: Class dictionary.
- :param list props: List of properties or ``None``.
- """
- if props is not None:
- for prop in props:
- if not isinstance(prop, (basestring, tuple, list)):
- raise errors.LmiCommandInvalidProperty(
- dcl['__module__'], name,
- 'PROPERTIES must be a list of strings or tuples')
- if isinstance(prop, (tuple, list)):
- if ( len(prop) != 2
- or not isinstance(prop[0], basestring)
- or ( not callable(prop[1])
- and not isinstance(prop[1], basestring))):
- raise errors.LmiCommandInvalidProperty(
- dcl['__module__'], name,
- 'tuples in PROPERTIES must be: ("name",'
- ' callable or property_name)')
-
-def _handle_render_properties(name, bases, dcl, target_formatter_lister=False):
- """
- Process properties related to rendering function for commands operating
- on CIM instances. Result of this function a ``render()`` and
- ``get_columns()`` functions being added to class's dictionary with
- regard to handled properties.
-
- Currently handled properties are:
-
- ``DYNAMIC_PROPERTIES`` : ``bool``
- Whether the associated function itself provides list of
- properties. Optional property.
- ``PROPERTIES`` : ``bool``
- List of instance properties to print. Optional property.
-
- :param string name: Name of class to be created.
- :param tuple bases: Base classes of new command.
- :param dictionary dcl: Class dictionary being modified by this method.
- :param boolean target_formatter_lister: Whether the output is targeted
- for *Show* command or *Lister*. The former expects a pair of
- column_names and values. The latter expects just values.
- """
- dynamic_properties = dcl.pop('DYNAMIC_PROPERTIES', False)
- if dynamic_properties and 'PROPERTIES' in dcl:
- raise errors.LmiCommandError(
- dcl['__module__'], name,
- 'DYNAMIC_PROPERTIES and PROPERTIES are mutually exclusive')
-
- properties = dcl.pop('PROPERTIES', None)
- _check_render_properties(name, dcl, properties)
-
- renderer = None
- get_columns = lambda cls: None
- if properties is None and not dynamic_properties:
- if ( target_formatter_lister
- and dcl.get('__metaclass__', None) is not InstanceListerMetaClass):
- raise errors.LmiCommandError(dcl['__module__'], name,
- "either PROPERTIES must be declared or"
- " DYNAMIC_PROPERTIES == True for InstanceLister"
- " commands")
- renderer = _make_render_all_properties(bases)
- elif properties is None and dynamic_properties:
- def _render_dynamic(self, return_value):
- """ Renderer of dynamic properties. """
- properties, inst = return_value
- return _make_render_with_properties(properties,
- target_formatter_lister)(self, inst)
- renderer = _render_dynamic
- elif properties is not None:
- renderer = _make_render_with_properties(properties,
- target_formatter_lister)
- get_columns = (lambda cls:
- tuple((p[0] if isinstance(p, tuple) else p)
- for p in properties))
- if renderer is not None:
- dcl['render'] = classmethod(renderer)
- if target_formatter_lister:
- dcl['get_columns'] = get_columns
-
-def _handle_opt_preprocess(name, dcl):
- """
- Process properties, that cause modification of parsed argument names before
- passing them to ``verify_options()`` or ``transform_options()``. If any of
- handled properties is supplied, it causes ``_preprocess_options()`` to be
- overriden, where all of desired name modifications will be made.
- Currently handled properties are:
-
- ``OPT_NO_UNDERSCORES`` : ``bool``
- When making a function's parameter name out of option, the leading
- dashes are replaced with underscore. If this property is True,
- dashes will be removed completely with no replacement.
- ``ARG_ARRAY_SUFFIX`` : ``bool``
- Add given suffix to all arguments resulting in list objects.
-
- :param string name: Command class name.
- :param dictionary dcl: Class dictionary being modified by this method.
- """
- if ( dcl.get('__metaclass__', None) is not EndPointCommandMetaClass
- and '_preprocess_options' in dcl):
- raise errors.LmiCommandError(dcl['__module__'], name,
- '_preprocess_options() method must not be overriden in the'
- 'body of command class; use transform_options() instead')
- arr_suffix = dcl.pop('ARG_ARRAY_SUFFIX', '')
- if ( not isinstance(arr_suffix, str)
- or not RE_ARRAY_SUFFIX.match(arr_suffix)):
- raise errors.LmiCommandInvalidProperty(dcl['__module__'], name,
- 'ARG_ARRAY_SUFFIX must be a string matching regular'
- ' expression "%s"' % RE_ARRAY_SUFFIX.pattern)
- opt_no_underscores = dcl.pop('OPT_NO_UNDERSCORES', False)
- if arr_suffix or opt_no_underscores:
- def _new_preprocess_options(_self, options):
- """ Modify (in-place) given options dictionary by renaming keys. """
- for do_it, cond, transform in (
- ( arr_suffix
- , lambda _, v: isinstance(v, list)
- , lambda n :
- ('<' + util.RE_OPT_BRACKET_ARGUMENT.match(n)
- .group(1) + arr_suffix + '>')
- if util.RE_OPT_BRACKET_ARGUMENT.match(n)
- else (n + arr_suffix))
- , ( opt_no_underscores
- , lambda n, _: RE_OPTION.match(n)
- , lambda n : RE_OPTION.match(n).group('name'))
- ):
- if not do_it:
- continue
- to_rename = ( name for name, value in options.items()
- if cond(name, value))
- for name in to_rename:
- new_name = transform(name)
- LOG().debug('Renaming option "%s" to "%s".', name, new_name)
- if new_name in options:
- LOG().warn(
- 'Existing option named "%s" replaced with "%s".',
- new_name, name)
- options[new_name] = options.pop(name)
-
- dcl['_preprocess_options'] = _new_preprocess_options
-
-def _handle_fallback_command(name, bases, dcl):
- """
- Process ``FALLBACK_COMMAND`` property of multiplexer command. It's turned
- into a :py:meth:`~.multiplexer.LmiCommandMultiplexer.fallback_command`
- class method. It needs to be called after the usage string is handled.
-
- .. seealso::
- :py:func:`_handle_usage`
- """
- fallback = dcl.pop('FALLBACK_COMMAND', None)
- if fallback is not None:
- if not issubclass(type(fallback), EndPointCommandMetaClass):
- raise errors.LmiCommandInvalidProperty(dcl['__module__'], name,
- "FALLBACK_COMMAND must be a command class"
- " (subclass of LmiEndPointCommand) not %s" % repr(fallback))
- if not fallback.has_own_usage():
- usage_string = dcl.get('__doc__', None)
- if not usage_string:
- for base_cls in bases:
- if not issubclass(base_cls, base.LmiBaseCommand):
- continue
- cmd = base_cls
- while not cmd.has_own_usage() and cmd.parent is not None:
- cmd = cmd.parent
- usage_string = cmd.__doc__
- if not usage_string:
- errors.LmiCommandError(dcl['__module__'], name,
- "Missing usage string.")
- fallback.__doc__ = usage_string
- fallback.has_own_usage = lambda cls: True
- dcl['fallback_command'] = staticmethod(lambda: fallback)
-
-def _handle_format_options(name, bases, dcl):
- """
- Process any ``FMT_*`` properties. This overrides ``format_options``
- property which returns dictionary of arguments passed to formatter factory.
-
- These properties are removed from class' dictionary.
- """
- format_options = {}
- for key, value in dcl.items():
- if key.startswith("FMT_"):
- opt_name = key[4:].lower()
- if opt_name not in FORMAT_OPTIONS:
- raise errors.LmiCommandInvalidProperty(
- dcl['__module__'], name,
- 'Formatting option "%s" is not supported.' %
- opt_name)
- if ( opt_name in ('no_headings', 'human_friendly')
- and not isinstance(value, bool)):
- raise errors.LmiCommandInvalidProperty(
- dcl['__module__'], name,
- '"%s" property must be a boolean')
- format_options[opt_name] = value
-
- if format_options:
- def _new_format_options(self):
- """ :returns: Dictionary of options for formatter object. """
- basecls = [b for b in bases if issubclass(b, base.LmiBaseCommand)][0]
- opts = basecls.format_options.fget(self)
- opts.update(format_options)
- return opts
- if 'format_options' in dcl:
- raise errors.LmiCommandError(dcl['__module__'], name,
- 'can not define both FMT_ options and "format_options" in'
- ' the same class, choose just one of them')
- dcl['format_options'] = property(_new_format_options)
-
- for key in format_options:
- dcl.pop('FMT_' + key.upper())
-
-def _handle_select(name, dcl):
- """
- Process properties of :py:class:`.select.LmiSelectCommand`.
- Currently handled properties are:
-
- ``SELECT`` : ``list``
- Is a list of pairs ``(condition, command)`` where ``condition`` is
- an expression in *LMIReSpL* language. And ``command`` is either a
- string with absolute path to command that shall be loaded or the
- command class itself.
-
- Small example: ::
-
- SELECT = [
- ( 'OpenLMI-Hardware < 0.4.2'
- , 'lmi.scripts.hardware.pre042.PreCmd'
- )
- , ('OpenLMI-Hardware >= 0.4.2 & class LMI_Chassis == 0.3.0'
- , HwCmd
- )
- ]
-
- It says: Let the ``PreHwCmd`` command do the job on brokers having
- ``openlmi-hardware`` package older than ``0.4.2``. Use the
- ``HwCmd`` anywhere else where also the ``LMI_Chassis`` CIM class in
- version ``0.3.0`` is available.
-
- First matching condition wins and assigned command will be passed
- all the arguments.
-
- ``DEFAULT`` : ``str`` or :py:class:`~.base.LmiBaseCommand`
- Defines fallback command used in case no condition can be
- satisfied.
-
- They will be turned into ``get_conditionals()`` method.
- """
- module_name = dcl.get('__module__', name)
- if not 'SELECT' in dcl:
- raise errors.LmiCommandError(module_name, name,
- "Missing SELECT property.")
- def inv_prop(msg, *args):
- return errors.LmiCommandInvalidProperty(module_name, name, msg % args)
- expressions = dcl.pop('SELECT')
- if not isinstance(expressions, (list, tuple)):
- raise inv_prop('SELECT must be list or tuple.')
- if len(expressions) < 1:
- raise inv_prop('SELECT must contain at least one condition!')
- for index, item in enumerate(expressions):
- if not isinstance(item, tuple):
- raise inv_prop('Items of SELECT must be tuples, not %s!' %
- getattr(type(item), '__name__', 'UNKNOWN'))
- if len(item) != 2:
- raise inv_prop('Expected pair in SELECT on index %d!' % index)
- expr, cmd = item
- if not isinstance(expr, basestring):
- raise inv_prop('Expected expression string on index %d'
- ' in SELECT!' % index)
- if isinstance(cmd, basestring) and not RE_MODULE_PATH.match(cmd):
- raise inv_prop('Second item of conditional pair on index %d'
- ' in SELECT does not look as an importable path!' % cmd)
- if ( not isinstance(cmd, basestring)
- and not issubclass(cmd, (basestring, base.LmiBaseCommand))):
- raise inv_prop('Expected subclass of LmiBaseCommand (or its import'
- ' path) as a second item of a pair on index %d in SELECT!'
- % index)
-
- default = dcl.pop('DEFAULT', None)
- if isinstance(default, basestring) and not RE_MODULE_PATH.match(default):
- raise inv_prop('DEFAULT "%s" does not look as an importable path!'
- % default)
- if ( default is not None and not isinstance(default, basestring)
- and not issubclass(default, (basestring, base.LmiBaseCommand))):
- raise inv_prop('Expected subclass of LmiBaseCommand'
- ' (or its import path) as a value of DEFAULT!')
- def _new_get_conditionals(self):
- return expressions, default
-
- dcl['get_conditionals'] = _new_get_conditionals
-
-class EndPointCommandMetaClass(abc.ABCMeta):
- """
- End point command does not have any subcommands. It's a leaf of
- command tree. It wraps some function in command library being
- referred to as an *associated function*. It handles following class
- properties:
-
- ``CALLABLE`` : ``str`` or callable
- An associated function. Mandatory property.
- ``OWN_USAGE`` : ``bool`` or ``str``
- Usage string. Optional property.
- ``ARG_ARRAY_SUFFIX`` : ``str``
- Suffix added to argument names containing array of values.
- Optional property.
- ``FMT_NO_HEADINGS`` : ``bool``
- Allows to force printing of table headers on and off for
- this command. Default is to print them.
- ``FMT_HUMAN_FRIENDLY`` : ``bool``
- Tells formatter to make the output more human friendly. The result
- is dependent on the type of formatter used.
- """
-
- def __new__(mcs, name, bases, dcl):
- _handle_usage(name, bases, dcl)
- _handle_callable(name, bases, dcl)
- _handle_opt_preprocess(name, dcl)
- _handle_format_options(name, bases, dcl)
-
- cls = super(EndPointCommandMetaClass, mcs).__new__(
- mcs, name, bases, dcl)
-
- # make additional check for arguments count
- dest = getattr(cls.execute, "dest", cls.execute)
- argspec = inspect.getargspec(dest)
- if ( not argspec.varargs
- and len(argspec.args) < cls.dest_pos_args_count()):
- raise errors.LmiCommandInvalidCallable(
- dcl['__module__'], name,
- 'Callable must accept at least %d positional arguments' %
- cls.dest_pos_args_count())
-
- return cls
-
-class SessionCommandMetaClass(EndPointCommandMetaClass):
- """
- Meta class for commands operating upon a session object.
- All associated functions take as first argument an namespace abstraction
- of type ``lmi.shell``.
-
- Handles following class properties:
-
- ``NAMESPACE`` : ``str``
- CIM namespace abstraction that will be passed to associated
- function. Defaults to ``"root/cimv2"``. If ``False``, raw
- :py:class:`lmi.shell.LMIConnection` object will be passed to
- associated function.
- """
- def __new__(mcs, name, bases, dcl):
- _handle_usage(name, bases, dcl)
- _handle_namespace(dcl)
- _handle_callable(name, bases, dcl)
-
- return EndPointCommandMetaClass.__new__(mcs, name, bases, dcl)
-
-class ListerMetaClass(SessionCommandMetaClass):
- """
- Meta class for end-point lister commands. Handles following class
- properties:
-
- ``COLUMNS`` : ``tuple``
- List of column names. Optional property. There are special values
- such as:
-
- ``None`` or omitted
- Associated function provides column names in a first row of
- returned list or generator.
-
- empty list, empty tuple or ``False``
- They mean that no headers shall be printed. It is simalar
- to using ``FMT_NO_HEADINGS = True``. But in this case all
- the rows returned from associated functions are treated as
- data.
- """
-
- def __new__(mcs, name, bases, dcl):
- cols = dcl.pop('COLUMNS', None)
- if cols is not None:
- if not isinstance(cols, (list, tuple)):
- raise errors.LmiCommandInvalidProperty(dcl['__module__'], name,
- 'COLUMNS class property must be either list or tuple')
- if len(cols) < 1 or cols is False:
- dcl['FMT_NO_HEADINGS'] = True
- cols = tuple()
- elif not all(isinstance(c, basestring) for c in cols):
- raise errors.LmiCommandInvalidProperty(dcl['__module__'], name,
- 'COLUMNS must contain just column names as strings')
- def _new_get_columns(_cls):
- """ Return column names. """
- return cols
- dcl['get_columns'] = classmethod(_new_get_columns)
-
- return super(ListerMetaClass, mcs).__new__(mcs, name, bases, dcl)
-
-class ShowInstanceMetaClass(SessionCommandMetaClass):
- """
- Meta class for end-point show instance commands. Additional handled
- properties:
-
- ``DYNAMIC_PROPERTIES`` : ``bool``
- Whether the associated function itself provides list of
- properties. Optional property.
- ``PROPERTIES`` : ``tuple``
- List of instance properties to print. Optional property.
-
- These are translated in a :py:meth:`~.show.LmiShowInstance.render`, which
- should be marked as abstract in base lister class.
- """
-
- def __new__(mcs, name, bases, dcl):
- _handle_render_properties(name, bases, dcl)
-
- return super(ShowInstanceMetaClass, mcs).__new__(
- mcs, name, bases, dcl)
-
-class InstanceListerMetaClass(SessionCommandMetaClass):
- """
- Meta class for instance lister command handling the same properties
- as :py:class:`ShowInstanceMetaClass`.
- """
-
- def __new__(mcs, name, bases, dcl):
- _handle_render_properties(name, bases, dcl, True)
-
- return super(InstanceListerMetaClass, mcs).__new__(
- mcs, name, bases, dcl)
-
-class CheckResultMetaClass(SessionCommandMetaClass):
- """
- Meta class for end-point command "check result". Additional handled
- properties:
-
- ``EXPECT`` :
- Value to compare against the return value. Mandatory property.
-
- ``EXPECT`` property is transformed into a
- :py:meth:`.checkresult.LmiCheckResult.check_result` method taking two
- arguments ``(options, result)`` and returning a boolean.
- """
-
- def __new__(mcs, name, bases, dcl):
- try:
- expect = dcl['EXPECT']
- if callable(expect):
- def _new_expect(_self, options, result):
- """
- Comparison function testing return value with *expect*
- function.
- """
- if isinstance(result, LMIReturnValue):
- result = result.rval
- passed = expect(options, result)
- if not passed:
- LOG().info('Got unexpected result "%s".')
- return passed
- else:
- def _new_expect(_self, _options, result):
- """ Comparison function testing by equivalence. """
- if isinstance(result, LMIReturnValue):
- result = result.rval
- passed = expect == result
- if not passed:
- LOG().info('Expected "%s", got "%s".', expect, result)
- return (False, '%s != %s' % (expect, result))
- return passed
- _new_expect.expected = expect
- del dcl['EXPECT']
- dcl['check_result'] = _new_expect
- except KeyError:
- # EXPECT might be defined in some subclass
- pass
-
- return super(CheckResultMetaClass, mcs).__new__(mcs, name, bases, dcl)
-
-class MultiplexerMetaClass(abc.ABCMeta):
- """
- Meta class for node command (not an end-point command). It handles
- following class properties:
-
- ``COMMANDS`` : ``dict``
- Command names with assigned command classes. Each of them is a
- direct subcommands of command with this property. Mandatory
- property.
-
- ``FALLBACK_COMMAND`` : :py:class:`~.endpoint.LmiEndPointCommand`
- Command factory to use in case that no command is passed on command
- line.
-
- Formatting options (starting with ``FMT_`` are also accepted, and may used
- to set defaults for all subcommands.
- """
-
- def __new__(mcs, name, bases, dcl):
- if dcl.get('__metaclass__', None) is not MultiplexerMetaClass:
- module_name = dcl.get('__module__', name)
- # check COMMANDS property and make it a classmethod
- if not 'COMMANDS' in dcl:
- raise errors.LmiCommandError(module_name, name,
- 'Missing COMMANDS property.')
- cmds = dcl.pop('COMMANDS')
- if not isinstance(cmds, dict):
- raise errors.LmiCommandInvalidProperty(module_name, name,
- 'COMMANDS must be a dictionary')
- if not all(isinstance(c, basestring) for c in cmds.keys()):
- raise errors.LmiCommandInvalidProperty(module_name, name,
- 'Keys of COMMANDS dictionary must contain command'
- ' names as strings.')
- for cmd_name, cmd in cmds.items():
- if not util.RE_COMMAND_NAME.match(cmd_name):
- raise errors.LmiCommandInvalidName(
- module_name, name, cmd_name)
- if not issubclass(cmd, base.LmiBaseCommand):
- raise errors.LmiCommandError(module_name, name,
- 'COMMANDS dictionary must be composed of'
- ' LmiBaseCommand subclasses, failed class: "%s"'
- % cmd.__name__)
- if cmd.is_multiplexer() and not cmd.has_own_usage():
- LOG().warn('Command "%s.%s" is missing usage string.'
- ' It will be inherited from parent command.',
- cmd.__module__, cmd.__name__)
- cmd.__doc__ = dcl['__doc__']
- def _new_child_commands(_cls):
- """ Returns list of subcommands. """
- return cmds
- dcl['child_commands'] = classmethod(_new_child_commands)
-
- _handle_usage(name, bases, dcl)
- _handle_fallback_command(name, bases, dcl)
- _handle_format_options(name, bases, dcl)
-
- return super(MultiplexerMetaClass, mcs).__new__(mcs, name, bases, dcl)
-
-class SelectMetaClass(abc.ABCMeta):
- """
- Meta class for select commands with guarded commands. Additional handled
- properties:
-
- ``SELECT`` : ``list``
- List of commands guarded with expressions representing requirements
- on server's side that need to be met.
- ``DEFAULT`` : ``str`` or :py:class:`~.base.LmiBaseCommand`
- Defines fallback command used in case no condition can is
- satisfied.
- """
-
- def __new__(mcs, name, bases, dcl):
- if dcl.get('__metaclass__', None) is not SelectMetaClass:
- module_name = dcl.get('__module__', name)
- if not '__doc__' in dcl:
- LOG().warn('Command selector "%s.%s" is missing short'
- ' description string (__doc__).',
- module_name, name)
- default = dcl.get('DEFAULT', None)
- if ( default is not None
- and issubclass(default, base.LmiBaseCommand)
- and getattr(dcl['DEFAULT'], '__doc__', None)):
- LOG().warn('Using __doc__ string from default command for'
- ' selector "%s.%s".', module_name, name)
- dcl['__doc__'] = dcl['DEFAULT'].__doc__
- _handle_select(name, dcl)
- return super(SelectMetaClass, mcs).__new__(mcs, name, bases, dcl)
-
diff --git a/lmi/scripts/common/command/multiplexer.py b/lmi/scripts/common/command/multiplexer.py
deleted file mode 100644
index 61ebcd7..0000000
--- a/lmi/scripts/common/command/multiplexer.py
+++ /dev/null
@@ -1,155 +0,0 @@
-# Copyright (C) 2013-2014 Red Hat, Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and documentation are
-# those of the authors and should not be interpreted as representing official
-# policies, either expressed or implied, of the FreeBSD Project.
-#
-# Authors: Michal Minar <miminar@redhat.com>
-#
-"""
-Defines command class used to nest multiple commands under one.
-"""
-
-from docopt import docopt
-
-from lmi.scripts.common import get_logger
-from lmi.scripts.common.command import base
-from lmi.scripts.common.command import meta
-
-LOG = get_logger(__name__)
-
-class LmiCommandMultiplexer(base.LmiBaseCommand):
- """
- Base class for node commands. It consumes just part of command line
- arguments and passes the remainder to one of its subcommands.
-
- Example usage: ::
-
- class MyCommand(LmiCommandMultiplexer):
- '''
- My command description.
-
- Usage: %(cmd)s mycommand (subcmd1 | subcmd2)
- '''
- COMMANDS = {'subcmd1' : Subcmd1, 'subcmd2' : Subcmd2}
-
- Where ``Subcmd1`` and ``Subcmd2`` are some other ``LmiBaseCommand``
- subclasses. Documentation string must be parseable with ``docopt``.
-
- Recognized properties:
-
- ``COMMANDS`` : ``dictionary``
- property will be translated to
- :py:meth:`LmiCommandMultiplexer.child_commands` class method by
- :py:class:`~.meta.MultiplexerMetaClass`.
-
- Using metaclass: :py:class:`.meta.MultiplexerMetaClass`.
- """
- __metaclass__ = meta.MultiplexerMetaClass
-
- @classmethod
- def child_commands(cls):
- """
- Abstract class method, that needs to be implemented in subclass.
- This is done by associated meta-class, when defining a command with
- assigned ``COMMANDS`` property.
-
- :returns: Dictionary of subcommand names with assigned command
- factories.
- :rtype: dictionary
- """
- raise NotImplementedError("child_commands must be implemented in"
- " a subclass")
-
- @classmethod
- def fallback_command(cls):
- """
- This is overriden by :py:class:`~.meta.MultiplexerMetaClass` when
- the ``FALLBACK_COMMAND`` gets processed.
-
- :returns: Command factory invoked for missing command on command line.
- :rtype: :py:class:`~.endpoint.LmiEndPointCommand`
- """
- return None
-
- @classmethod
- def is_end_point(cls):
- return False
-
- def run_subcommand(self, cmd_name, args):
- """
- Pass control to a subcommand identified by given name.
-
- :param string cmd_name: Name of direct subcommand, whose
- :py:meth:`~.base.LmiBaseCommand.run` method shall be invoked.
- :param list args: List of arguments for particular subcommand.
- :returns: Application exit code.
- :rtype: integer
- """
- if not isinstance(cmd_name, basestring):
- raise TypeError("cmd_name must be a string, not %s" %
- repr(cmd_name))
- if not isinstance(args, (list, tuple)):
- raise TypeError("args must be a list, not %s" % repr(args))
- try:
- cmd_cls = self.child_commands()[cmd_name]
- cmd = cmd_cls(self.app, cmd_name, self)
- except KeyError:
- self.app.stderr.write(self.get_usage())
- LOG().critical('Unexpected command "%s".', cmd_name)
- return 1
- return cmd.run(args)
-
- def run(self, args):
- """
- Handle optional parameters, retrieve desired subcommand name and
- pass the remainder of arguments to it.
-
- :param list args: List of arguments with at least subcommand name.
- """
- if not isinstance(args, (list, tuple)):
- raise TypeError("args must be a list")
- full_args = self.get_cmd_name_parts(for_docopt=True) + args
- docopt_kwargs = {
- # check the --help ourselves (the default docopt behaviour checks
- # also for --version)
- 'help' : False,
- # let's ignore options following first command for generated
- # usage string and when a height of this branch is > 2
- 'options_first' : not self.has_own_usage()
- or any( not cmd.is_end_point()
- for cmd in self.child_commands().values()
- if not args or args[0] not in self.child_commands()
- or self.child_commands()[args[0]] is cmd)
- }
- options = docopt(self.get_usage(), full_args, **docopt_kwargs)
- if options.pop('--help', False) or (args and args[0] == '--help'):
- self.app.stdout.write(self.get_usage(proper=True))
- return 0
- if ( self.fallback_command() is not None
- and (not args or args[0] not in self.child_commands())):
- cmd_cls = self.fallback_command()
- cmd = cmd_cls(self.app, self.cmd_name, self.parent)
- return cmd.run(args)
- return self.run_subcommand(args[0], args[1:])
diff --git a/lmi/scripts/common/command/select.py b/lmi/scripts/common/command/select.py
deleted file mode 100644
index 5f89994..0000000
--- a/lmi/scripts/common/command/select.py
+++ /dev/null
@@ -1,195 +0,0 @@
-# Copyright (C) 2013-2014 Red Hat, Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and documentation are
-# those of the authors and should not be interpreted as representing official
-# policies, either expressed or implied, of the FreeBSD Project.
-#
-# Authors: Michal Minar <miminar@redhat.com>
-#
-"""
-Defines command class used to choose other commands depending on
-profile and class requirements.
-"""
-
-from docopt import docopt
-from pyparsing import ParseException
-
-from lmi.scripts.common import get_logger
-from lmi.scripts.common import errors
-from lmi.scripts.common.command import base
-from lmi.scripts.common.command import meta
-from lmi.scripts.common.session import SessionProxy
-from lmi.scripts.common.versioncheck import eval_respl
-
-class LmiSelectCommand(base.LmiBaseCommand):
- """
- Base class for command selectors. It does not process command line
- arguments. Thery are passed unchanged to selected command whose
- requirements are met. Its doc string is not interpreted in any way.
-
- If there are more hosts, conditions are evaluated per each. They are then
- split into groups, each fulfilling particular condition. Associated
- commands are then invoked on these groups separately.
-
- Example usage: ::
-
- class MySelect(LmiSelectCommand):
- SELECT = [
- ( 'OpenLMI-Hardware >= 0.4.2'
- , 'lmi.scripts.hardware.current.Cmd'),
- ('OpenLMI-Hardware', 'lmi.scripts.hardware.pre042.Cmd')
- ]
- DEFAULT = MissingHwProviderCmd
-
- Using metaclass: :py:class:`.meta.SelectMetaClass`.
- """
- __metaclass__ = meta.SelectMetaClass
-
- @classmethod
- def is_end_point(cls):
- return False
-
- @classmethod
- def is_multiplexer(cls):
- return False
-
- @classmethod
- def get_conditionals(cls):
- """
- Get the expressions with associated commands. This shall be overriden
- by a subclass.
-
- :returns: Pair of ``(expressions, default)``.
- Where ``expressions`` is a list of pairs ``(condition, command)``.
- And ``default`` is the same as ``command`` used in case no
- ``condition`` is satisfied.
- :rtype: list
- """
- raise NotImplementedError(
- "get_conditionals needs to be defined in subclass")
-
- def eval_expr(self, expr, hosts, cache=None):
- """
- Evaluate expression on group of hosts.
-
- :param string expr: Expression to evaluate.
- :param list hosts: Group of hosts that shall be checked.
- :param dictionary cache: Optional cache object speeding up evaluation
- by reducing number of queries to broker.
- :returns: Subset of hosts satisfying *expr*.
- :rtype: list
- """
- if cache is None:
- cache = dict()
- session = self.session
- satisfied = []
- try:
- for host in hosts: # TODO: could be done concurrently
- conn = session[host]
- if not conn:
- continue
- if eval_respl(expr, conn, cache=cache):
- satisfied.append(host)
- except ParseException:
- raise errors.LmiBadSelectExpression(self.__class__.__module__,
- self.__class__.__name__, "Bad select expression: %s" % expr)
- return satisfied
-
- def select_cmds(self, cache=None):
- """
- Generator of command factories with associated groups of hosts. It
- evaluates given expressions on session. In this process all expressions
- from :py:meth:`get_conditionals` are checked in a row. Host satisfying
- some expression is added to group associated with it and is excluded
- from processing following expressions.
-
- :param dictionary cache: Optional cache object speeding up the evaluation
- by reducing number of queries to broker.
- :returns: Pairs in form ``(command_factory, session_proxy)``.
- :rtype: generator
- :raises:
- * :py:class:`~lmi.scripts.common.errors.LmiUnsatisfiedDependencies`
- if no condition is satisfied for at least one host. Note that
- this exception is raised at the end of evaluation. This lets
- you choose whether you want to process satisfied hosts - by
- processing the generator at once. Or whether you want to be
- sure it is satisfied by all of them - you turn the generator
- into a list.
- * :py:class:`~lmi.scripts.common.errors.LmiNoConnections`
- if no successful connection was done.
- """
- if cache is None:
- cache = dict()
- conds, default = self.get_conditionals()
- def get_cmd_factory(cmd):
- if isinstance(cmd, basestring):
- i = cmd.rindex('.')
- module = __import__(cmd[:i], fromlist=cmd[i+1:])
- return getattr(module, cmd[i+1:])
- else:
- return cmd
-
- session = self.session
- unsatisfied = set(session.hostnames)
-
- for expr, cmd in conds:
- hosts = self.eval_expr(expr, unsatisfied, cache)
- if hosts:
- yield get_cmd_factory(cmd), SessionProxy(session, hosts)
- hosts = set(hosts).union(set(session.get_unconnected()))
- unsatisfied.difference_update(hosts)
- if not unsatisfied:
- break
- if default is not None and unsatisfied:
- yield get_cmd_factory(default), SessionProxy(session, unsatisfied)
- unsatisfied.clear()
- if len(unsatisfied):
- raise errors.LmiUnsatisfiedDependencies(unsatisfied)
- if len(session) == len(session.get_unconnected()):
- raise errors.LmiNoConnections("No successful connection!")
-
- def get_usage(self, proper=False):
- """
- Try to get usage of any command satisfying some expression.
-
- :raises: Same exceptions as :py:meth:`select_cmds`.
- """
- for cmd_cls, _ in self.select_cmds():
- cmd = cmd_cls(self.app, self.cmd_name, self.parent)
- return cmd.get_usage(proper)
-
- def run(self, args):
- """
- Iterate over command factories with associated sessions and
- execute them with unchanged *args*.
- """
- result = 0
- for cmd_cls, session in self.select_cmds():
- cmd = cmd_cls(self.app, self.cmd_name, self.parent)
- cmd.set_session_proxy(session)
- ret = cmd.run(args)
- if result == 0:
- result = ret
- return result
-
diff --git a/lmi/scripts/common/command/session.py b/lmi/scripts/common/command/session.py
deleted file mode 100644
index 9d88bbe..0000000
--- a/lmi/scripts/common/command/session.py
+++ /dev/null
@@ -1,206 +0,0 @@
-# Copyright (C) 2013-2014 Red Hat, Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and documentation are
-# those of the authors and should not be interpreted as representing official
-# policies, either expressed or implied, of the FreeBSD Project.
-#
-# Authors: Michal Minar <miminar@redhat.com>
-#
-"""
-Defines a base class for all command classes operating upon a
-:py:class:`~lmi.scripts.common.session.Session` object.
-"""
-import abc
-from collections import OrderedDict
-
-from lmi.scripts.common import get_logger
-from lmi.scripts.common.command import meta
-from lmi.scripts.common.command.endpoint import LmiEndPointCommand
-from lmi.scripts.common.configuration import Configuration
-from lmi.scripts.common.session import Session
-from lmi.shell import LMIConnection
-from lmi.shell import LMIUtil
-
-LOG = get_logger(__name__)
-
-class LmiSessionCommand(LmiEndPointCommand):
- """
- Base class for end-point commands operating upon a session object.
-
- Using metaclass: :py:class:`~.meta.SessionCommandMetaClass`.
- """
- __metaclass__ = meta.SessionCommandMetaClass
-
- @classmethod
- def cim_namespace(cls):
- """
- This returns default cim namespace, the connection object will be
- nested into before being passed to associated function.
- It can be overriden in few ways:
-
- 1. by setting ``[CIM] Namespace`` option in configuration
- 2. by giving ``--namespace`` argument on command line to the
- ``lmi`` meta-command
- 3. by setting ``NAMESPACE`` property in declaration of command
-
- Higher number means higher priority.
- """
- return Configuration.get_instance().namespace
-
- @classmethod
- def dest_pos_args_count(cls):
- """
- There is a namespace/connection object passed as the first positional
- argument.
- """
- return LmiEndPointCommand.dest_pos_args_count.im_func(cls) + 1
-
- def process_session(self, session, args, kwargs):
- """
- Process each host of given session, call the associated command
- function, collect results and print it to standard output.
-
- :param session: Session object with set of hosts.
- :type session: :py:class:`~lmi.scripts.common.session.Session`
- :param list args: Positional arguments to pass to associated function
- in command library.
- :param dictionary kwargs: Keyword arguments as a dictionary.
- :returns: Exit code of application.
- :rtype: integer
- """
- if not isinstance(session, Session):
- raise TypeError("session must be an object of Session, not %s"
- % repr(session))
- # { ( hostname : (passed, result) ), ... }
- # where passed is a boolean and result is returned value if passed is
- # True and exception otherwise
- results = OrderedDict()
- for connection in session:
- try:
- result = self.take_action(connection, args, kwargs)
- # result may be a generator which may throw in the following
- # function
- results[connection.uri] = (True, result)
- self.process_host_result(connection.uri, True, result)
- except Exception as exc:
- if len(session) > 1:
- LOG().exception('Invocation failed for host "%s": %s',
- connection.uri, exc)
- else:
- LOG().exception(exc)
- results[connection.uri] = (False, exc)
- self.process_host_result(connection.uri, False, exc)
- self.process_session_results(session, results)
- return all(r[0] for r in results.values())
-
- def process_host_result(self, hostname, success, result):
- """
- Called from :py:meth:`process_session` after single host gets
- processed. By default this prints obtained *result* with default
- formatter if the execution was successful. Children of this class may
- want to override this.
-
- :param string hostname: Name of host involved.
- :param boolean success: Whether the action on host succeeded.
- :param result: Either the value returned by associated function upon a
- successful invocation or an exception.
- """
- if success:
- if len(self.session) > 1:
- self.formatter.print_host(hostname)
- self.produce_output(result)
-
- def process_session_results(self, session, results):
- """
- Called at the end of :py:meth:`process_session`'s execution. It's
- supposed to do any summary work upon results from all hosts. By default
- it just prints errors in a form of list.
-
- :param session: Session object.
- :type session: :py:class:`lmi.scripts.common.session.Session`
- :param dictionary results: Dictionary of form: ::
-
- { 'hostname' : (success_flag, result), ... }
-
- where *result* is either an exception or returned value of
- associated function, depending on *success_flag*. See the
- :py:meth:`process_host_result`.
- """
- if not isinstance(session, Session):
- raise TypeError("session must be a Session object")
- if not isinstance(results, dict):
- raise TypeError("results must be a dictionary")
- # check whether any output has been produced
- if ( len(session.get_unconnected())
- or any(not r[0] for r in results.values())):
- data = []
- for hostname in session.get_unconnected():
- data.append((hostname, ['failed to connect']))
- for hostname, (success, error) in results.items():
- if not success:
- data.append((hostname, [error]))
- if len(session) > 1:
- self._print_errors(data,
- new_line=any(r[0] for r in results.values()))
-
- @abc.abstractmethod
- def take_action(self, connection, args, kwargs):
- """
- Executes an action on single host and collects results.
-
- :param connection: Connection to a single host.
- :type connection: :py:class:`lmi.shell.LMIConnection`
- :param list args: Positional arguments for associated function.
- :param dictionary kwargs: Keyword arguments for associated function.
- :returns: Column names and item list as a pair.
- :rtype: tuple
- """
- raise NotImplementedError("take_action must be implemented in subclass")
-
- def execute_on_connection(self, connection, *args, **kwargs):
- """
- Wraps the :py:meth:`~.endpoint.LmiEndPointCommand.execute` method with
- connection adjustments. Connection object is not usually passed
- directly to associated function. Mostly it's the namespace object that
- is expected. This method checks, whether the namespace object is
- desired and modifies connection accordingly.
-
- :param connection: Connection to single host.
- :type connection: :py:class:`lmi.shell.LMIConnection`
- :param list args: Arguments handed over to associated function.
- :param dictionary kwargs: Keyword arguments handed over to associated
- function.
- """
- if not isinstance(connection, LMIConnection):
- raise TypeError("expected an instance of LMIConnection for"
- " connection argument, not %s" % repr(connection))
- namespace = self.cim_namespace()
- if namespace is not None:
- connection = LMIUtil.lmi_wrap_cim_namespace(
- connection, namespace)
- return self.execute(connection, *args, **kwargs)
-
- def run_with_args(self, args, kwargs):
- return self.process_session(self.session, args, kwargs)
-
diff --git a/lmi/scripts/common/command/show.py b/lmi/scripts/common/command/show.py
deleted file mode 100644
index 1698fec..0000000
--- a/lmi/scripts/common/command/show.py
+++ /dev/null
@@ -1,112 +0,0 @@
-# Copyright (C) 2013-2014 Red Hat, Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and documentation are
-# those of the authors and should not be interpreted as representing official
-# policies, either expressed or implied, of the FreeBSD Project.
-#
-# Authors: Michal Minar <miminar@redhat.com>
-#
-"""
-Contains command classes producing key-value pairs to output.
-"""
-import abc
-
-from lmi.scripts.common import formatter
-from lmi.scripts.common import get_logger
-from lmi.scripts.common.command import meta
-from lmi.scripts.common.command.session import LmiSessionCommand
-
-LOG = get_logger(__name__)
-
-class LmiShowInstance(LmiSessionCommand):
- """
- End point command producing a list of properties of particular CIM
- instance. Either reduced list of properties to print can be specified, or
- the associated function alone can decide, which properties shall be
- printed. Associated function is expected to return CIM instance as a
- result.
-
- List of additional recognized properties:
-
- ``DYNAMIC_PROPERTIES`` : ``bool``
- A boolean saying, whether the associated function alone shall
- specify the list of properties of rendered instance. If ``True``,
- the result of function must be a pair: ::
-
- (props, inst)
-
- Where props is the same value as can be passed to ``PROPERTIES``
- property. Defaults to ``False``.
- ``PROPERTIES`` : ``tuple``
- May contain list of instance properties, that will be produced in
- the same order as output. Each item of list can be either:
-
- name : ``str``
- Name of property to render.
- pair : ``tuple``
- A tuple ``(Name, render_func)``, where former item an
- arbitraty name for rendered value and the latter is a
- function taking as the only argument particular instance
- and returning value to render.
-
- ``DYNAMIC_PROPERTIES`` and ``PROPERTIES`` are mutually exclusive. If none
- is given, all instance properties will be printed.
-
- Using metaclass: :py:class:`~.meta.ShowInstanceMetaClass`.
- """
- __metaclass__ = meta.ShowInstanceMetaClass
-
- def formatter_factory(self):
- return formatter.SingleFormatter
-
- @abc.abstractmethod
- def render(self, result):
- """
- This method can either be overriden in a subclass or left alone. In the
- latter case it will be generated by
- :py:class:`~.meta.ShowInstanceMetaClass` metaclass with regard to
- ``PROPERTIES`` and ``DYNAMIC_PROPERTIES``.
-
- :param result: Either an instance to
- render or pair of properties and instance.
- :type: :py:class:`lmi.shell.LMIInstance` or ``tuple``
- :returns: List of pairs, where the first item is a label and second a
- value to render.
- :rtype: list
- """
- raise NotImplementedError(
- "render method must be overriden in subclass")
-
- def take_action(self, connection, args, kwargs):
- """
- Process single connection to a host, render result and return a value
- to render.
-
- :returns: List of pairs, where the first item is a label and
- second a value to render.
- :rtype: list
- """
- res = self.execute_on_connection(connection, *args, **kwargs)
- return self.render(res)
-
diff --git a/lmi/scripts/common/command/util.py b/lmi/scripts/common/command/util.py
deleted file mode 100644
index 914850a..0000000
--- a/lmi/scripts/common/command/util.py
+++ /dev/null
@@ -1,109 +0,0 @@
-# Copyright (C) 2013-2014 Red Hat, Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# The views and conclusions contained in the software and documentation are
-# those of the authors and should not be interpreted as representing official
-# policies, either expressed or implied, of the FreeBSD Project.
-#
-# Authors: Michal Minar <miminar@redhat.com>
-#
-"""
-Utility functions used in command sub-package.
-"""
-
-import inspect
-import os
-import re
-
-#: Regular expression matching bracket argument such as ``<arg_name>``.
-RE_OPT_BRACKET_ARGUMENT = re.compile('^<(?P<name>[^>]+)>$')
-#: Regular expression matching argument written in upper case such as
-#:``ARG_NAME``.
-RE_OPT_UPPER_ARGUMENT = re.compile('^(?P<name>[A-Z0-9]+(?:[_-][A-Z0-9]+)*)$')
-#: Regular expression matching showt options. They are one character
-#: long, prefixed with single dash.
-RE_OPT_SHORT_OPTION = re.compile('^-(?P<name>[a-z])$', re.IGNORECASE)
-#: Regular expression matching long options (prefixed with double dash).
-RE_OPT_LONG_OPTION = re.compile('^--(?P<name>[a-z0-9_-]+)$', re.IGNORECASE)
-#: Command name can also be a single or double dash.
-RE_COMMAND_NAME = re.compile(r'^([a-z]+(-[a-z0-9]+)*|--?)$')
-
-def is_abstract_method(clss, method, missing_is_abstract=False):
- """
- Check, whether the given method is abstract in given class or list of
- classes. May be used to check, whether we should override particular
- abstract method in a meta-class in case that no non-abstract
- implementation is defined.
-
- :param clss: Class or list of classes that is
- searched for non-abstract implementation of particular method.
- If the first class having particular method in this list contain
- non-abstract implementation, ``False`` is returned.
- :type clss: type or tuple
- :param string method: Name of method to look for.
- :param boolean missing_is_abstract: This is a value returned, when
- not such method is defined in a set of given classes.
- :returns: Are all occurences of given method abstract?
- :rtype: boolean
- """
- if ( not isinstance(clss, (list, tuple, set))
- and not isinstance(clss, type)):
- raise TypeError("clss must be either a class or a tuple of classes")
- if not isinstance(method, basestring):
- raise TypeError("method must be a string")
- if isinstance(clss, type):
- clss = [clss]
- for cls in clss:
- if hasattr(cls, method):
- if getattr(getattr(cls, method), "__isabstractmethod__", False):
- return True
- else:
- return False
- return missing_is_abstract
-
-def get_module_name(frame_level=2):
- """
- Get a module name of caller from particular outer frame.
-
- :param integer frame_level: Number of nested frames to skip when searching
- for called function scope by inspecting stack upwards. When the result
- of this function is applied directly on the definition of function,
- it's value should be 1. When used from inside of some other factory, it
- must be increased by 1.
-
- Level 0 returns name of this module. Level 1 returns module name of
- caller. Level 2 returns module name of caller's caller.
- :returns: Module name.
- :rtype: string
- """
- frame = inspect.currentframe()
- while frame_level > 0 and frame.f_back:
- frame = frame.f_back
- frame_level -= 1
- module = getattr(frame, 'f_globals', {}).get('__name__', None)
- if module is None:
- if hasattr(frame, 'f_code'):
- module = os.path.basename(frame.f_code.co_filename.splitext())[0]
- else:
- module = '_unknown_'
- return module