summaryrefslogtreecommitdiffstats
path: root/lmi
diff options
context:
space:
mode:
Diffstat (limited to 'lmi')
-rw-r--r--lmi/__init__.py30
-rw-r--r--lmi/scripts/__init__.py30
-rw-r--r--lmi/scripts/_metacommand/__init__.py239
-rw-r--r--lmi/scripts/_metacommand/cmdutil.py68
-rw-r--r--lmi/scripts/_metacommand/exit.py74
-rw-r--r--lmi/scripts/_metacommand/help.py101
-rw-r--r--lmi/scripts/_metacommand/interactive.py475
-rw-r--r--lmi/scripts/_metacommand/manager.py182
-rw-r--r--lmi/scripts/_metacommand/toplevel.py184
-rw-r--r--lmi/scripts/_metacommand/util.py264
-rw-r--r--lmi/scripts/common/__init__.py73
-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
-rw-r--r--lmi/scripts/common/configuration.py311
-rw-r--r--lmi/scripts/common/errors.py140
-rw-r--r--lmi/scripts/common/formatter/__init__.py484
-rw-r--r--lmi/scripts/common/formatter/command.py71
-rw-r--r--lmi/scripts/common/lmi_logging.py256
-rw-r--r--lmi/scripts/common/session.py190
-rw-r--r--lmi/scripts/common/util.py146
-rw-r--r--lmi/scripts/common/versioncheck/__init__.py146
-rw-r--r--lmi/scripts/common/versioncheck/parser.py521
32 files changed, 0 insertions, 6812 deletions
diff --git a/lmi/__init__.py b/lmi/__init__.py
deleted file mode 100644
index ca65877..0000000
--- a/lmi/__init__.py
+++ /dev/null
@@ -1,30 +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>
-#
-__import__('pkg_resources').declare_namespace(__name__)
diff --git a/lmi/scripts/__init__.py b/lmi/scripts/__init__.py
deleted file mode 100644
index ca65877..0000000
--- a/lmi/scripts/__init__.py
+++ /dev/null
@@ -1,30 +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>
-#
-__import__('pkg_resources').declare_namespace(__name__)
diff --git a/lmi/scripts/_metacommand/__init__.py b/lmi/scripts/_metacommand/__init__.py
deleted file mode 100644
index affea7b..0000000
--- a/lmi/scripts/_metacommand/__init__.py
+++ /dev/null
@@ -1,239 +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>
-#
-"""
-Subpackage containing functionality of lmi meta-command.
-"""
-
-import argparse
-import logging
-import sys
-
-from lmi.scripts import common
-from lmi.scripts.common import errors
-from lmi.scripts._metacommand import util
-from lmi.scripts._metacommand import exit
-from lmi.scripts._metacommand.help import Help
-from lmi.scripts._metacommand.manager import CommandManager
-from lmi.scripts._metacommand.interactive import Interactive
-from lmi.scripts._metacommand.toplevel import TopLevelCommand
-from lmi.scripts.common.command import LmiCommandMultiplexer, LmiBaseCommand
-from lmi.scripts.common.configuration import Configuration
-from lmi.scripts.common.session import Session
-from lmi.shell import LMIUtil
-
-LOG = common.get_logger(__name__)
-
-# write errors to stderr until logging is configured
-logging.getLogger('').addHandler(logging.StreamHandler())
-
-class MetaCommand(object):
- """
- Main application class. It instantiates configuration object, logging and
- then it passes control to commands.
-
- Example usage:
-
- MetaCommand().run()
- """
-
- def __init__(self):
- # allow exceptions in lmi shell
- LMIUtil.lmi_set_use_exceptions(True)
- # instance of CommandManager, created when first needed
- self._command_manager = None
- self.stdout = sys.stdout
- self.stderr = sys.stderr
- self.stdin = sys.stdin
- # instance of Session, created when needed
- self._session = None
- # instance of Configuration, created in setup()
- self.config = None
- # dictionary of not yet processed options, it's created in setup()
- self._options = None
- self._active_command = None
-
- def _configure_logging(self):
- """
- Setup logging. It expects Configuration object to be already
- initialized.
-
- Logging can be tuned in various ways:
-
- * In configuration file with options:
- * [Main] Verbosity
- * [Log] OutputFile
- * [Log] FileFormat
- * [Log] ConsoleFormat
- * [Log] ConsoleInfoFormat
- * [Log] LogToConsole
- * With command line options:
- ``-v`` flags :
- Each such flag increases logging level of what is logged
- into console. This overrides `[Main] Verbosity` option.
- ``-q`` :
- Causes supression of any output made to stdout except for
- error messages. This overrides ``[Main] Verbosity``.
- option and ``-v`` flags.
- ``--log-file`` :
- Output file for logging messages. This overrides ``[Log]
- OutputFile`` option.
-
- Implicitly only warnings and errors are logged to the standard error
- stream without any tracebacks.
- """
- util.setup_logging(self.config, self.stderr)
-
- @property
- def command_manager(self):
- """
- Return instance of ``CommandManager``. It's initialized when first
- needed.
-
- :rtype: (``CommandManager``)
- """
- if self._command_manager is None:
- self._command_manager = CommandManager()
- self._command_manager.add_command('help', Help)
- return self._command_manager
-
- @property
- def session(self):
- """
- Return instance of Session. Instantiated when first needed.
-
- :rtype: (``Session``)
- """
- if self._session is None:
- if ( not self._options['--host']
- and not self._options['--hosts-file']):
- self._options['--host'] = [util.get_default_hostname()]
- LOG().info('No hosts given, using "%s".',
- self._options['--host'][0])
- hostnames = []
- # credentials loaded from file
- credentials = {}
- def add_hosts(hosts, creds):
- """ Update hostnames and credentials for new data. """
- hostnames.extend(hosts)
- credentials.update(creds)
- if self._options['--hosts-file']:
- hosts_path = self._options['--hosts-file']
- try:
- with open(hosts_path, 'r') as hosts_file:
- add_hosts(*util.parse_hosts_file(hosts_file))
- except (OSError, IOError) as err:
- LOG().critical('Could not read hosts file "%s": %s',
- hosts_path, err)
- sys.exit(1)
- add_hosts(*util.get_hosts_credentials(self._options['--host']))
- if self._options['--user']:
- credentials.update({
- # credentials in file has precedence over --user option
- h : credentials.get(h, (self._options['--user'], ''))
- for h in hostnames if h not in credentials
- })
- self._session = Session(self, hostnames, credentials,
- same_credentials=self._options['--same-credentials'])
- return self._session
-
- @property
- def active_command(self):
- return self._active_command
- @active_command.setter
- def active_command(self, cmd):
- if not isinstance(cmd, LmiBaseCommand):
- raise TypeError("cmd must be an instance of LmiBaseCommand")
- self._active_command = cmd
-
- def print_version(self):
- """ Print version of this egg to stdout. """
- self.stdout.write("%s\n" % util.get_version())
-
- def setup(self, options):
- """
- Initialise global Configuration object and set up logging.
-
- :param options: (``dict``) Dictionary of options parsed from command
- line by docopt.
- """
- conf_kwargs = {}
- if options['--config-file']:
- conf_kwargs['user_config_file_path'] = options.pop('--config-file')
- self.config = Configuration.get_instance(**conf_kwargs)
- # two mutually exclusive options
- if options['--trace'] or options['--notrace']:
- self.config.trace = bool(options['--trace'])
- if options.pop('--quiet', False):
- self.config.verbosity = Configuration.OUTPUT_SILENT
- elif options['-v'] and options['-v'] > 0:
- self.config.verbosity = options['-v']
- if options.pop('--noverify', False):
- self.config.verify_server_cert = False
- self.config.log_file = options.pop('--log-file', None)
- self._configure_logging()
- del options['--trace']
- del options['--notrace']
- del options['-v']
- self.config.namespace = options.pop('--namespace', None)
- self.config.human_friendly = options.pop('--human-friendly', None)
- self.config.no_headings = options.pop('--no-headings', None)
- self.config.lister_format = options.pop('--lister-format', None)
- # unhandled options may be used later (for session creation),
- # so let's save them
- self._options = options
-
- def run(self, argv):
- """
- Equivalent to the main program for the application.
-
- :param argv: (``list``) Input arguments and options.
- Contains all arguments but the application name.
- """
- retval = exit.EXIT_CODE_FAILURE
- cmd = TopLevelCommand(self)
- try:
- retval = cmd.run(argv)
- except Exception as exc:
- if isinstance(exc, errors.LmiUnsatisfiedDependencies):
- retval = exit.EXIT_CODE_UNSATISFIED_DEPENDENCIES
- LOG().exception(str(exc))
- if isinstance(retval, bool) or not isinstance(retval, (int, long)):
- return ( exit.EXIT_CODE_SUCCESS if bool(retval) or retval is None
- else exit.EXIT_CODE_FAILURE)
-
- return retval
-
-def main(argv=sys.argv[1:]):
- """
- Main entry point function. It just passes arguments to instantiated
- ``MetaCommand``.
- """
- return MetaCommand().run(argv)
-
diff --git a/lmi/scripts/_metacommand/cmdutil.py b/lmi/scripts/_metacommand/cmdutil.py
deleted file mode 100644
index 986e835..0000000
--- a/lmi/scripts/_metacommand/cmdutil.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright (c) 2013, 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 for command inspection.
-"""
-from lmi.scripts.common import errors
-from lmi.scripts.common.command import LmiBaseCommand
-from lmi.scripts.common.command import LmiCommandMultiplexer
-
-def get_subcommand_names(command):
- """
- :param command: Either a multiplexer command or top-level one.
- :returns: Names of children commands.
- :rtype: list
- """
- if not isinstance(command, LmiBaseCommand):
- raise TypeError("command must be an instance of LmiBaseCommand")
- if isinstance(command, LmiCommandMultiplexer):
- return command.child_commands().keys()
- if command.parent is None: # top level command
- return command.app.command_manager.command_names
- raise ValueError("command must be either multiplexer or top-level command")
-
-def get_subcommand_factory(command, name):
- """
- :param command: Either a multiplexer command or top-level one.
- :returns: Callable returning an instance of
- :py:class:`~lmi.scripts.common.command.multiplexer.LmiCommandMultiplexer`
- :rtype: callable
- """
- if isinstance(command, LmiCommandMultiplexer):
- try:
- return command.child_commands()[name]
- except KeyError:
- cmd_path = command.cmd_name_parts
- cmd_path.append(name)
- raise errors.LmiCommandNotFound(" ".join(cmd_path))
- if command.parent is None: # top level command
- return command.app.command_manager.find_command(name)
- raise ValueError("command must be either multiplexer or top-level command")
-
diff --git a/lmi/scripts/_metacommand/exit.py b/lmi/scripts/_metacommand/exit.py
deleted file mode 100644
index e876073..0000000
--- a/lmi/scripts/_metacommand/exit.py
+++ /dev/null
@@ -1,74 +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 containing help command.
-"""
-
-from lmi.scripts.common import get_logger
-from lmi.scripts.common.command import LmiEndPointCommand
-from lmi.scripts.common import errors
-
-LOG = get_logger(__name__)
-
-EXIT_CODE_SUCCESS = 0
-EXIT_CODE_FAILURE = 1
-EXIT_CODE_KEYBOARD_INTERRUPT = 2
-EXIT_CODE_COMMAND_NOT_FOUND = 3
-EXIT_CODE_INVALID_SYNTAX = 4
-EXIT_CODE_UNSATISFIED_DEPENDENCIES = 5
-
-def _execute_exit(exit_code):
- """ Associated function with ``Exit`` command. """
- raise errors.LmiTerminate(exit_code)
-
-class Exit(LmiEndPointCommand):
- """
- Terminate the shell.
-
- Usage: %(cmd)s [<exit_code>]
- """
- CALLABLE = _execute_exit
- OWN_USAGE = True
-
- def verify_options(self, options):
- code = options['<exit_code>']
- if code is not None:
- try:
- int(code)
- except ValueError:
- raise errors.LmiInvalidOptions(
- "<exit_code> must be an integer not \"%s\"" % code)
-
- def transform_options(self, options):
- code = options.get('<exit_code>', None)
- if code is None:
- code = EXIT_CODE_SUCCESS
- options['<exit_code>'] = int(code)
-
diff --git a/lmi/scripts/_metacommand/help.py b/lmi/scripts/_metacommand/help.py
deleted file mode 100644
index b88f6b5..0000000
--- a/lmi/scripts/_metacommand/help.py
+++ /dev/null
@@ -1,101 +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 containing help command.
-"""
-
-from lmi.scripts.common import errors
-from lmi.scripts.common import get_logger
-from lmi.scripts.common.command import LmiEndPointCommand
-from lmi.scripts._metacommand import cmdutil
-from lmi.scripts._metacommand import exit
-
-LOG = get_logger(__name__)
-
-class Help(LmiEndPointCommand):
- """
- Print the list of supported commands with short description.
- If a subcommand is given, its detailed help will be printed.
-
- Usage: %(cmd)s [<subcommand>...]
- """
- OWN_USAGE = True
-
- def execute(self, subcommand):
- mgr = self.app.command_manager
- node = self.app.active_command
- toplevel = self
- while toplevel.parent is not None:
- toplevel = toplevel.parent
-
- if node or subcommand:
- # Help for some subcommand will be printed.
- if node is None:
- node = toplevel
- if subcommand:
- index = 0
- try:
- while index < len(subcommand) and not node.is_end_point():
- while node.is_selector():
- cmd_factory, _ = node.select_cmds().next()
- node = cmd_factory(self.app, node.cmd_name,
- node.parent)
- # selector may return either multiplexer or end-point
- if node.is_end_point():
- break
- cmd_factory = cmdutil.get_subcommand_factory(node,
- subcommand[index])
- node = cmd_factory(self.app, subcommand[index], node)
- index += 1
- except errors.LmiCommandNotFound as err:
- LOG().error(str(err))
- if node is not toplevel:
- self.app.stdout.write(node.get_usage(True))
- if node is self.app.active_command:
- # show additional information only when no command given
- self.app.stdout.write('\nTo get help for built-in commands,'
- ' type:\n :help\n')
- return exit.EXIT_CODE_SUCCESS
-
- # let's print the summary of available commands
- self.app.stdout.write("Commands:\n")
- max_cmd_len = max(len(n) for n in mgr)
- cmd_line = " %%-%ds - %%s\n" % max_cmd_len
- for cmd in sorted(mgr):
- self.app.stdout.write(cmd_line
- % (cmd, mgr[cmd].get_description()
- .strip().split("\n", 1)[0]))
- self.app.stdout.write(
- "\nFor more informations about particular command type:\n"
- " help <command>\n")
-
- return exit.EXIT_CODE_SUCCESS
-
diff --git a/lmi/scripts/_metacommand/interactive.py b/lmi/scripts/_metacommand/interactive.py
deleted file mode 100644
index 349d622..0000000
--- a/lmi/scripts/_metacommand/interactive.py
+++ /dev/null
@@ -1,475 +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 interactive application.
-"""
-
-import cmd
-import docopt
-import itertools
-import os
-import readline
-import shlex
-
-from lmi.scripts.common import errors
-from lmi.scripts.common import get_logger
-from lmi.scripts.common.command import LmiBaseCommand, LmiCommandMultiplexer
-from lmi.scripts._metacommand import cmdutil
-from lmi.scripts._metacommand import exit
-
-LOG = get_logger(__name__)
-
-BUILT_INS_USAGE = """
-Built-ins.
-
-Usage:
- : cd [<path>]
- : ..
- : pwd
- : (help | -h | --help)
-
-Description:
- cd Nest to a subcommand. Accepts a sequence of subcommands separated
- with "/". If "/" is given, top-level command becomes the active one.
- Other supported special symbols are ".." and ".".
- .. Make parent command the active one. Same as issuing ":cd ..".
- pwd Print current command path.
- help Show this help.
-"""
-
-BUILT_INS = ('..', 'cd', 'pwd', 'help')
-
-class LmiBuiltInError(errors.LmiError):
- """ General exception concerning the use of built-in commands. """
- pass
-
-class LmiCanNotNest(LmiBuiltInError):
- """ Raised upon invalid use of *cd* built-in command. """
- pass
-
-class Interactive(cmd.Cmd):
- """
- Launched by the main application. It enters *shell* mode. Allows to launch
- set of commands interactively on all given hosts. Session object stays the
- same for the whole life of interactive mode.
-
- :param top_level_cmd: Top-level command.
- :type top_level_cmd: :py:class:`.toplevel.TopLevelCommand`
- """
-
- def __init__(self, top_level_cmd):
- self._top_level_cmd = top_level_cmd
- cmd.Cmd.__init__(self,
- stdin=top_level_cmd.app.stdin, stdout=top_level_cmd.app.stdout)
- self._last_exit_code = exit.EXIT_CODE_SUCCESS
- self.doc_header = 'Static commands'
- self.app.active_command = top_level_cmd
- self.load_history()
-
- # *************************************************************************
- # Properties
- # *************************************************************************
- @property
- def app(self):
- """ :returns: Application object. """
- return self._top_level_cmd.app
-
- @property
- def command_manager(self):
- """ :returns: An instance of :py:class:`~.manager.CommandManager`. """
- return self.app.command_manager
-
- @property
- def on_top_level_node(self):
- """
- :returns: Whether the current node is a top-level one. In other words
- we're not nested in any subcommand namespace.
- :rtype: boolean
- """
- return self._top_level_cmd is self.app.active_command
-
- @property
- def prompt(self):
- """
- :returns: Dynamically computed shell prompt.
- :rtype: string
- """
- if self.app.stdin.isatty():
- parents_num = 0
- node = self.app.active_command
- while node.parent is not None:
- parents_num += 1
- node = node.parent
- return '>'*parents_num + self.app.active_command.cmd_name + '> '
- return ''
-
- # *************************************************************************
- # Private methods
- # *************************************************************************
- def _change_to_node(self, path):
- """
- Handles any command path. It constructs an object of command
- corresponding to given path. Path can be absolute or relative.
-
- :param str path: Path to command. Looks similar to unix file path.
- Command names are separated with ``'/'``.
- :returns: Multiplexer command corresponding to path.
- :rtype: :py:class:`~lmi.scripts.common.command.multiplexer.LmiCommandMultiplexer`
- """
- cur_node = self.app.active_command
- if path.startswith('/'):
- if path.startswith('/lmi'):
- path = path[4:]
- else:
- path = path[1:]
- cur_node = self._top_level_cmd
-
- cmd_chain = os.path.normpath(path).split('/')
- for subcmd in cmd_chain:
- if subcmd == '..':
- cur_node = ( cur_node.parent
- if cur_node.parent is not None else cur_node)
- elif subcmd and subcmd != '.':
- if not subcmd in cmdutil.get_subcommand_names(cur_node):
- raise LmiCanNotNest('No such subcommand "%s".' %
- "/".join(cmd_chain))
- cmd_cls = cmdutil.get_subcommand_factory(cur_node, subcmd)
- if not issubclass(cmd_cls, LmiCommandMultiplexer):
- raise LmiCanNotNest('Can not nest to subcommand "%s" which'
- " is not a multiplexer." % "/".join(cmd_chain))
- if not cmd_cls.has_own_usage():
- raise LmiCanNotNest('Can not nest to subcommand "%s" which'
- ' lacks any help message.' % "/".join(cmd_chain))
- cur_node = cmd_cls(self.app, subcmd, cur_node)
- self.app.active_command = cur_node
- return exit.EXIT_CODE_SUCCESS
-
- def _do_built_in_cmd(self, args):
- """
- Execute built-in command.
-
- :param list args: Command arguments including command name.
- """
- options = docopt.docopt(BUILT_INS_USAGE, args, help=False)
- if options['cd']:
- path = '.' if not options.get('<path>', None) else options['<path>']
- return self._change_to_node(path)
- elif options['..']:
- return self._change_to_node('..')
- elif options['pwd']:
- self.app.stdout.write("/"
- + "/".join(self.app.active_command.get_cmd_name_parts(
- all_parts=True)) + "\n")
- elif options['help'] or options['-h'] or options['--help']:
- self.app.stdout.write(BUILT_INS_USAGE[1:])
- return exit.EXIT_CODE_SUCCESS
-
- def _execute_line_parts(self, line_parts):
- """
- Try to execute given line. This method can throw various exceptions
- that needs to be handled by a caller, otherwise interactive mode will
- be terminated.
-
- :param list line_parts: Parsed command line arguments.
- :returns: Command's exit code.
- """
- if line_parts[0][0] == ':':
- if len(line_parts[0]) > 1:
- line_parts[0] = line_parts[0][1:]
- else:
- line_parts = line_parts[1:]
- return self._do_built_in_cmd(line_parts)
-
- else:
- # let's try to run registered subcommand
- retval = self.run_subcommand(line_parts)
- if isinstance(retval, bool) or not isinstance(retval, (int, long)):
- retval = ( exit.EXIT_CODE_SUCCESS
- if bool(retval) or retval is None
- else exit.EXIT_CODE_FAILURE)
- return retval
-
- # *************************************************************************
- # Public methods
- # *************************************************************************
- def complete(self, text, state):
- """
- Overrides parent's method so that registered commands can be also
- completed.
- """
- if state == 0:
- import readline
- origline = readline.get_line_buffer()
- line = origline.lstrip()
- stripped = len(origline) - len(line)
- begidx = readline.get_begidx() - stripped
- endidx = readline.get_endidx() - stripped
- command, _, _ = self.parseline(line)
- compfunc = self.completedefault
- if command and hasattr(self, 'complete_' + command):
- compfunc = getattr(self, 'complete_' + command)
- self.completion_matches = compfunc(text, line, begidx, endidx)
- try:
- return self.completion_matches[state]
- except IndexError:
- return None
-
- def completedefault(self, text, line, *_args, **_kwargs):
- """
- Tab-completion for commands known to the command manager and subcommands
- in current command namespace. Does not handle command options.
- """
- if line.startswith(':'):
- commands = BUILT_INS
- else:
- commands = set(self.completenames(text))
- commands.update(set(cmdutil.get_subcommand_names(self.app.active_command)))
- completions = sorted(n for n in commands
- if not text or n.startswith(text))
- return completions
-
- def clear_history(self):
- """ Clear readline history. """
- readline.clear_history()
-
- def default(self, line):
- """
- This is run, when line contains unknown command to ``cmd.Cmd``. It
- expects us to handle it if we know it, or print an error.
-
- :param str line: Line given to our shell.
- """
- try:
- line_parts = shlex.split(line)
- except ValueError as err:
- LOG().error(str(err))
- return exit.EXIT_CODE_INVALID_SYNTAX
-
- try:
- self._execute_line_parts(line_parts)
-
- except docopt.DocoptExit as err:
- # command found, but options given to it do not comply with its
- # usage string
- if '--help' in line_parts:
- return self.do_help(" ".join(
- line_parts[:line_parts.index('--help')]))
- LOG().warn("Wrong options given: %s", line.strip())
- self.stdout.write(str(err))
- if ( line_parts[0] in cmdutil.get_subcommand_names(
- self.app.active_command)
- and cmdutil.get_subcommand_factory(self.app.active_command,
- line_parts[0]).is_end_point()):
- self.stdout.write("\n\nTo see a full usage string, type:\n"
- " help %s\n" % line_parts[0])
- else:
- self.stdout.write("\n\nTo see a list of available commands,"
- " type:\n help\n")
- self._last_exit_code = exit.EXIT_CODE_FAILURE
-
- except errors.LmiCommandNotFound as err:
- LOG().error(str(err))
- self._last_exit_code = exit.EXIT_CODE_COMMAND_NOT_FOUND
-
- except errors.LmiUnsatisfiedDependencies as err:
- LOG().error(str(err))
- self._last_exit_code = exit.EXIT_CODE_UNSATISFIED_DEPENDENCIES
-
- except errors.LmiError as err:
- LOG().error(str(err))
- self._last_exit_code = exit.EXIT_CODE_FAILURE
-
- except KeyboardInterrupt as err:
- LOG().debug('%s: %s', err.__class__.__name__, str(err))
- self._last_exit_code = exit.EXIT_CODE_KEYBOARD_INTERRUPT
-
- return self._last_exit_code
-
- def do_EOF(self, _arg): #pylint: disable=C0103,R0201
- """
- Exit on End-Of-File if we are on top-level command. Otherwise change
- to parent command.
- """
- if self.app.stdin.isatty():
- self.app.stdout.write('\n')
- if self.app.stdin.isatty() and not self.on_top_level_node:
- self._change_to_node('..')
- else:
- raise errors.LmiTerminate(self._last_exit_code)
-
- def do_exit(self, arg):
- """
- This makes the exit command work in both (non-)interactive modes.
- """
- command = ["exit"]
- if arg:
- command.append(arg)
- return self.run_subcommand(command)
-
- def do_help(self, arg):
- """ Handle help subcommand. """
- if arg:
- try:
- arg_parts = shlex.split(arg)
- except ValueError as err:
- LOG().error(str(err))
- return exit.EXIT_CODE_INVALID_SYNTAX
-
- method_name = '_'.join(
- itertools.chain(['do'],
- itertools.takewhile(lambda x: not x.startswith('-'),
- arg_parts)))
- if hasattr(self, method_name):
- return cmd.Cmd.do_help(self, arg)
- else:
- arg_parts = []
-
- if ( self.on_top_level_node
- and ( not arg
- or ( len(arg_parts) == 1
- and arg not in self.app.command_manager.command_names))):
- if not self.completenames(arg):
- LOG().error(str(errors.LmiCommandNotFound(arg)))
- cmd.Cmd.do_help(self, '')
- else:
- cmd.Cmd.do_help(self, arg)
- cmd_names = set(self.command_manager)
- cmd_names.difference_update(set(self.completenames('')))
- self.print_topics(
- "Application commands (type help <topic>):",
- sorted(cmd_names), 15, 80)
- self.print_topics(
- "Built-in commands (type :help):",
- [':'+bi for bi in BUILT_INS], 15, 80)
- return exit.EXIT_CODE_SUCCESS
-
- return self.run_subcommand(['help'] + arg_parts)
-
- def emptyline(self): #pylint: disable=R0201
- """ Do nothing for empty line. """
- pass
-
- def help_exit(self):
- """ Provide help for exit command. """
- cur_node = self.app.active_command
- # temporarily change to top level command where help cmd is registered
- self.app.active_command = self._top_level_cmd
- try:
- return self.run_subcommand(["help", "exit"])
- finally:
- self.app.active_command = cur_node
-
- def help_help(self):
- """
- Use the command manager to get instructions for "help".
- """
- cur_node = self.app.active_command
- # temporarily change to top level command where help cmd is registered
- self.app.active_command = self._top_level_cmd
- try:
- return self.run_subcommand(["help", "help"])
- finally:
- self.app.active_command = cur_node
-
- def load_history(self):
- """
- Load a readline history file.
- """
- if ( self.app.config.history_max_length == 0
- or not os.path.exists(self.app.config.history_file)):
- return
- LOG().debug('Reading history file "%s"', self.app.config.history_file)
- try:
- readline.read_history_file(self.app.config.history_file)
- if ( self.app.config.history_max_length > 0
- and readline.get_current_history_length()
- > self.app.config.history_max_length):
- readline.set_history_length(self.app.config.history_max_length)
- readline.write_history_file(self.app.config.history_file)
- readline.read_history_file(self.app.config.history_file)
- except (IOError, OSError) as err:
- LOG().warn('Failed to read history file "%s".',
- self.app.config.history_file, exc_info=err)
-
- def postcmd(self, stop, _line):
- """
- This is called after the ``do_*`` command to postprocess its result and
- decide whether to stop the shell. We want to stop only when
- :py:class:`lmi.scripts.common.errors.LmiError` is raised. This
- exception is catched upwards in call chain.
-
- :returns: Whether to stop the shell.
- :rtype: bool
- """
- return False
-
- def run_subcommand(self, args):
- """
- Run a subcommand given as a first item of ``args``. It must be one of
- commands registered in manager. Returns the return value of invoked
- command.
-
- :param list args: List of commands.
- """
- if not isinstance(args, (list, tuple)):
- raise TypeError("args must be a list")
- if len(args) < 1:
- raise ValueError("args must not be empty")
- try:
- cmd_factory = cmdutil.get_subcommand_factory(
- self.app.active_command, args[0])
- parent = self.app.active_command
- except errors.LmiCommandNotFound:
- # When nested into a subcommand, let it handle the args if known.
- # If not known, try one of static commands.
- if not self.on_top_level_node and hasattr(self, 'do_' + args[0]):
- cmd_factory = self.app.command_manager.find_command(args[0])
- parent = self._top_level_cmd
- else:
- raise
- cmd_inst = cmd_factory(self.app, args[0], parent)
- return cmd_inst.run(args[1:])
-
- def save_history(self):
- """
- Saves current history of commands into the history file. If the length
- of history exceeds a maximum history file length, the history will be
- truncated.
- """
- if self.app.config.history_max_length != 0:
- LOG().debug('Writing history file "%s"',
- self.app.config.history_file)
- if self.app.config.history_max_length > 0:
- readline.set_history_length(self.app.config.history_max_length)
- try:
- readline.write_history_file(self.app.config.history_file)
- except (IOError, OSError), err:
- LOG().warn('Failed to write history file "%s".',
- self.app.config.history_file, exc_info=err)
diff --git a/lmi/scripts/_metacommand/manager.py b/lmi/scripts/_metacommand/manager.py
deleted file mode 100644
index 773979b..0000000
--- a/lmi/scripts/_metacommand/manager.py
+++ /dev/null
@@ -1,182 +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>
-#
-"""
-Manager module for direct subcommands of lmi metacommand. Most of them are
-loaded from entry_points of installed python eggs.
-"""
-
-import pkg_resources
-
-from lmi.scripts.common import Configuration
-from lmi.scripts.common import errors
-from lmi.scripts.common import get_logger
-from lmi.scripts.common.command import base
-from lmi.scripts.common.command import util
-
-LOG = get_logger(__name__)
-
-class _CustomCommandWrapper(object):
- """
- Provide an interface mocking an entry_point object for custom commands
- added by lmi metacommand application.
-
- :param name: (``str``) Name of command.
- :param cmd_class: (``LmiBaseCommand``) Factory for custom commands.
- """
-
- def __init__(self, name, cmd_class):
- if not isinstance(name, basestring):
- raise TypeError("name must be a string")
- if not issubclass(cmd_class, base.LmiBaseCommand):
- raise TypeError("cmd_class must be a LmiBaseCommand")
- self._name = name
- self._cmd_class = cmd_class
-
- @property
- def name(self):
- """ Return command name. """
- return self._name
-
- def load(self):
- """ Return command class. """
- return self._cmd_class
-
-class CommandManager(object):
- """
- Manager of direct subcommands of lmi metacommand. It manages commands
- registered with entry_points under particular namespace installed by
- python eggs. Custom commands may also be added.
-
- :param namespace: (``str``) Namespace, where commands are registered.
- For example ``lmi.scripts.cmd``.
- """
-
- def __init__(self, namespace=None):
- if namespace is not None and not isinstance(namespace, basestring):
- raise TypeError("namespace must be a string")
- if namespace is None:
- namespace = Configuration.get_instance().get_safe(
- "Main", "CommandNamespace")
- self._namespace = namespace
- self._commands = {}
- self._load_commands()
-
- @property
- def command_names(self):
- """ Returns list of command names. """
- return self._commands.keys()
-
- def __len__(self):
- return len(self._commands)
-
- def __iter__(self):
- """ Yields command names. """
- return iter(self._commands)
-
- def __getitem__(self, cmd_name):
- """ Gets command factory for name. """
- return self.find_command(cmd_name)
-
- def _load_commands(self):
- """ Loads commands from entry points under provided namespace. """
- def _add_entry_point(epoint):
- """
- Convenience function taking an entry point, making some name
- checks and adding it to registered commands.
- """
- if not util.RE_COMMAND_NAME.match(epoint.name):
- LOG().error('Invalid command name: %s, ignoring.', epoint.name)
- return
- if epoint.name in self._commands:
- LOG().warn('Command "%s" already registered, ignoring.',
- epoint.name)
- else:
- LOG().debug('Found registered command "%s".', epoint.name)
- self._commands[epoint.name] = epoint
-
- for entry_point in pkg_resources.iter_entry_points(self._namespace):
- if isinstance(entry_point, dict):
- for epoint in entry_point.values():
- _add_entry_point(epoint)
- else:
- _add_entry_point(entry_point)
-
-
- def add_command(self, name, cmd_class):
- """
- Registers custom command. May be used for example for *help* command.
-
- :param name: (``str``) Name of command.
- :param cmd_class: (``LmiBaseCommand``) Factory for commands.
- """
- if not isinstance(name, basestring):
- raise TypeError("name must be a string")
- if not issubclass(cmd_class, base.LmiBaseCommand):
- raise TypeError("cmd_class must be a LmiBaseCommand")
- if not util.RE_COMMAND_NAME.match(name):
- raise errors.LmiCommandInvalidName(
- cmd_class.__module__, cmd_class.__class__.__name__, name)
- if name in self._commands:
- LOG().warn('Command "%s" already managed, overwriting with "%s:%s".',
- name, cmd_class.__module__, cmd_class.__name__)
- self._commands[name] = _CustomCommandWrapper(name, cmd_class)
-
- def find_command(self, cmd_name):
- """
- Loads a command associated with given name and returns it.
-
- :param cmd_name: (``str``) Name of command to load.
- :rtype: (``LmiBaseCommand``) Factory for commands.
- """
- try:
- return self._commands[cmd_name].load()
- except KeyError:
- raise errors.LmiCommandNotFound(cmd_name)
- except ImportError as err:
- LOG().debug('Failed to import command "%s".', cmd_name, exc_info=err)
- raise errors.LmiCommandImportError(
- cmd_name, self._commands[cmd_name].module_name, err)
-
- def reload_commands(self, keep_custom=True):
- """
- Flushes all commands and reloads entry points.
-
- :param keep_custom: (``bool``) Custom commands -- not loaded from
- entry points -- are preserved.
- """
- if keep_custom:
- keep = { n: c for n, c in self._commands.items()
- if isinstance(c, _CustomCommandWrapper)}
- else:
- keep = {}
- self._commands = {}
- self._load_commands()
- self._commands.update(keep)
-
diff --git a/lmi/scripts/_metacommand/toplevel.py b/lmi/scripts/_metacommand/toplevel.py
deleted file mode 100644
index 1e654a3..0000000
--- a/lmi/scripts/_metacommand/toplevel.py
+++ /dev/null
@@ -1,184 +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 the root command (``lmi`` binary).
-"""
-
-USAGE_STRING = \
-"""
-OpenLMI command line interface for CIM providers. It's functionality is
-composed of registered subcommands, operating on top of simple libraries,
-interfacing with particular OpenLMI profile providers.
-Works also in interactive mode which is entered, when <command> argument is
-omitted.
-
-Usage:
- %(cmd)s [options] [(--trace | --notrace)] [-v]... [-h <host>]...
- <command> [<args> ...]
- %(cmd)s [options] [(--trace | --notrace)] [-v]... [-h <host>]...
- %(cmd)s (--help | --version)
-
-Options:
- -c --config-file <config> Path to a user configuration file. Options
- specified here override any settings of global
- configuration file.
- -h --host <host> Hostname of target system.
- --hosts-file <hosts> Path to a file containing target hostnames.
- Each hostname must be listed on a single line.
- --user <user> Username used in connection to any target host.
- --same-credentials Use the first credentials given for all hosts.
- -n --noverify Do not verify cimom's ssl certificate.
- -v Increase verbosity of output.
- --trace Show tracebacks on errors.
- --notrace Suppress tracebacks for exceptions.
- -q --quiet Supress output except for errors.
- --log-file <log_file> Output file for logging messages.
- --namespace <namespace> Default CIM namespace to use.
- -N --no-headings Don't print table headings.
- -H --human-friendly Print large values in human friendly units (i.e.
- MB, GB, TB etc.)
- -L --lister-format (table | csv)
- Print output of lister commands in CSV or table
- format. CSV format is more suitable for machine
- processing. Defaults to table.
- --help Show this text and quit.
- --version Print version of '%(cmd)s' in use and quit.
-
-Handling hosts:
- If no --host or --hosts-file given the "localhost" is tried. When running
- under root with Pegasus CIMOM, this results in a connection over unix
- socket (without the need for credentials).
-
- Hosts may contain embedded credentials e.g.:
- http://user:passwd@hostname:5988
- Avoid supplying them on command line though since arguments are visible in
- process table. Use --hosts-file option instead.
-"""
-
-import docopt
-
-from lmi.scripts._metacommand import exit
-from lmi.scripts._metacommand import util
-from lmi.scripts._metacommand import Interactive
-from lmi.scripts.common import get_logger
-from lmi.scripts.common import errors
-from lmi.scripts.common.command import base
-
-LOG = get_logger(__name__)
-
-class TopLevelCommand(base.LmiBaseCommand):
- """
- Top level (instance, without any parent) command handling application
- parameters and passing work to registered subcommands.
- """
-
- @classmethod
- def has_own_usage(cls):
- return True
-
- @classmethod
- def child_commands(cls):
- return []
-
- @classmethod
- def is_end_point(cls):
- return False
-
- def __init__(self, app, cmd_name='lmi'):
- base.LmiBaseCommand.__init__(self, app, cmd_name)
-
- def get_usage(self, proper=False):
- return USAGE_STRING[1:] % { 'cmd' : " ".join(self.cmd_name_parts) }
-
- def run_subcommand(self, cmd_name, args):
- """
- Finds a command factory, instantiates it and passes the control.
- """
- cmd_factory = self.app.command_manager[cmd_name]
- cmd = cmd_factory(self.app, cmd_name, parent=self)
- return cmd.run(args)
-
- def start_interactive_mode(self):
- """ Run the command line loop of interactive application. """
- self.app.command_manager.add_command("exit", exit.Exit)
- iapp = Interactive(self)
- while True:
- try:
- ret = iapp.cmdloop()
- break
- except errors.LmiTerminate as err:
- ret = err.args[0]
- break
- except KeyboardInterrupt as err:
- LOG().debug('%s: %s', err.__class__.__name__, str(err))
- self.app.stdout.write('\n')
- iapp.save_history()
- return ret
-
- def run(self, args):
- """
- Handle program arguments, set up the application and call
- a subcommand or enter interactive mode. Return exit code.
-
- :param args: (``list``) Arguments without the binary name.
- """
- if not isinstance(args, (tuple, list)):
- raise TypeError("args must be a list")
- try:
- options = docopt.docopt(self.get_usage(), args,
- version=util.get_version(), help=False, options_first=True)
- except docopt.DocoptLanguageError as exc:
- self.app.stderr.write("%s\n" % str(exc))
- return exit.EXIT_CODE_FAILURE
- if options.pop('--help', False):
- self.app.stdout.write(self.get_usage())
- self.app.stdout.write("\nCommands:\n")
- self.app.stdout.write(" %s\n" % " ".join(
- n for n in sorted(self.app.command_manager)))
- return exit.EXIT_CODE_SUCCESS
- if options.pop('--version', False):
- self.app.print_version()
- return exit.EXIT_CODE_SUCCESS
- self.app.setup(options)
- if options['<command>'] is None:
- return self.start_interactive_mode()
-
- try:
- LOG().debug('Running command "%s".', options['<command>'])
- return self.run_subcommand(options['<command>'], options['<args>'])
- except docopt.DocoptExit as err:
- if '--help' in args:
- cmd_args = options['<args>']
- cmd_args = cmd_args[:cmd_args.index('--help')]
- return self.run_subcommand(
- 'help', [options['<command>']] + cmd_args)
- raise
-
-
diff --git a/lmi/scripts/_metacommand/util.py b/lmi/scripts/_metacommand/util.py
deleted file mode 100644
index 15686a3..0000000
--- a/lmi/scripts/_metacommand/util.py
+++ /dev/null
@@ -1,264 +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-command utility module.
-"""
-
-import logging
-import logging.config
-import os
-import pkg_resources
-import platform
-import re
-import socket
-import sys
-import urlparse
-
-from lmi.scripts.common import Configuration
-from lmi.scripts.common import get_logger
-from lmi.scripts.common import lmi_logging
-
-PYTHON_EGG_NAME = "openlmi-scripts"
-#: Service name identifying tcp port used to connect to CIMOM.
-DEFAULT_BROKER_SERVICE_NAME = 'wbem-https'
-
-RE_NETLOC = re.compile(r'^((?P<username>[^:@]+)(:(?P<password>[^@]+))?@)?'
- r'(?P<hostname>[^:]+)(:(?P<port>\d+))?$')
-
-VERBOSITY_2_LOG_LEVEL = {
- Configuration.OUTPUT_SILENT : logging.ERROR,
- Configuration.OUTPUT_WARNING : logging.WARNING,
- Configuration.OUTPUT_INFO : logging.INFO,
- Configuration.OUTPUT_DEBUG : logging.DEBUG,
-}
-
-DEFAULT_LOGGING_CONFIG = {
- 'version' : 1,
- 'disable_existing_loggers': True,
- 'formatters' : {
- 'console' : {
- "()" : "lmi.scripts.common.lmi_logging.LevelDispatchingFormatter",
- "formatters" : {
- logging.INFO :
- Configuration.default_options()['ConsoleInfoFormat']
- },
- 'default': Configuration.default_options()['ConsoleFormat'],
- 'datefmt' : '%Y-%m-%d %H:%M:%S'
- },
- 'file' : {
- 'format' : Configuration.default_options()['FileFormat'],
- 'datefmt' : '%Y-%m-%d %H:%M:%S'
- }
- },
- 'handlers' : {
- 'console': {
- 'class' : "logging.StreamHandler",
- 'level' : logging.ERROR,
- 'formatter': 'console',
- },
- 'console_shell': {
- 'class' : "logging.StreamHandler",
- 'level' : logging.CRITICAL,
- 'formatter': 'console',
- },
- 'file' : {
- 'class' : "logging.FileHandler",
- 'level' : Configuration.default_options()['Level'].upper(),
- 'formatter': 'file',
- }
- },
- 'loggers' : {
- 'lmi.shell' : {
- 'handlers' : ['console_shell'],
- 'level' : logging.DEBUG,
- 'propagate' : False
- }
- },
- 'root' : {
- 'level' : logging.DEBUG,
- 'handlers' : ['console']
- }
-}
-
-LOG = get_logger(__name__)
-
-def setup_logging(app_config, stderr=sys.stderr):
- """
- Setup logging to console and optionally to the file.
-
- :param app_config: (``lmi.scripts.common.Configuration``)
- Configuration object.
- :param stderr: (``file``) Output stream, where console handler should
- dispatch logging messages.
- """
- cfg = DEFAULT_LOGGING_CONFIG.copy()
-
- # Set up logging to a file
- if app_config.log_file:
- cfg['handlers']['file']['filename'] = app_config.log_file
- cfg['formatters']['file']['format'] = app_config.get_safe(
- 'Log', 'FileFormat', raw=True)
- try:
- cfg['handlers']['file']['level'] = \
- getattr(logging, app_config.logging_level.upper())
- except KeyError:
- LOG().error('Unsupported logging level: "%s".',
- app_config.logging_level)
- cfg['root']['handlers'].append('file')
- cfg['loggers']['lmi.shell']['handlers'].append('file')
- else:
- del cfg['formatters']['file']
- del cfg['handlers']['file']
-
- if app_config.get_safe('Log', "LogToConsole", bool):
- # Set up logging to console
- if stderr is not sys.stderr:
- cfg['handlers']['console']['stream'] = stderr
- cfg['handlers']['console']['level'] = \
- VERBOSITY_2_LOG_LEVEL[app_config.verbosity]
-
- # make the verbosity of lmi shell one level less
- lmi_shell_verbosity = app_config.verbosity
- if ( app_config.verbosity < app_config.OUTPUT_DEBUG
- and app_config.verbosity > app_config.OUTPUT_SILENT):
- lmi_shell_verbosity -= 1
- cfg['handlers']['console_shell']['level'] = \
- VERBOSITY_2_LOG_LEVEL[lmi_shell_verbosity]
-
- # use ConsoleInfoFormat for INFO and less severe levels
- cfg['formatters']['console']['formatters'] = {
- logging.INFO :
- app_config.get_safe('Log', 'ConsoleInfoFormat', raw=True)
- }
- # use ConsoleFormat for any other level
- cfg['formatters']['console']['default'] = app_config.get_safe(
- 'Log', 'ConsoleFormat', raw=True)
- else:
- cfg['root']['handlers'].remove('console')
- cfg['loggers']['lmi.shell']['handlers'].remove('console_shell')
- del cfg['handlers']['console']
- del cfg['handlers']['console_shell']
-
- use_colors = platform.system() != 'Windows' and stderr.isatty()
- lmi_logging.setup_logger(use_colors = use_colors)
- logging.config.dictConfig(cfg)
-
-def get_version(egg_name=PYTHON_EGG_NAME):
- """
- Gets version string of any python egg. Defaults to the egg of current
- application.
- """
- return pkg_resources.get_distribution(egg_name).version
-
-def get_hosts_credentials(hostnames):
- """
- Parse list of hostnames, get credentials out of them and return
- ``(hostnames, creds)``, where ``hostnames`` is a list of ``hostnames``
- with credentials removed and ``creds`` is a dictionary with a pair
- ``(username, password)`` for every hostname, that supplied it.
-
- :param hostnames: (``list``) List of hostnames with optional credentials.
- For example: ``http://root:password@hostname:5988``.
- """
- if not hasattr(hostnames, '__iter__'):
- raise TypeError("hostnames must be a list of hosts")
- new_hostnames = []
- credentials = {}
- for hostname in hostnames:
- parsed = urlparse.urlparse(hostname)
- if not parsed.netloc and parsed.path:
- # got something like [user[:pass]@]hostname[:port] (no scheme)
- match = RE_NETLOC.match(hostname)
- if match:
- hostname = match.group('hostname')
- if match.group('port'):
- hostname += ':' + match.group('port')
- if match.group('username') or match.group('password'):
- credentials[hostname] = (
- match.group('username'), match.group('password'))
- elif parsed.username or parsed.password:
- hostname = parsed.scheme
- if parsed.scheme:
- hostname += "://"
- hostname += parsed.hostname
- if parsed.port:
- hostname += ":" + str(parsed.port)
- hostname += parsed.path
- credentials[hostname] = (parsed.username, parsed.password)
- new_hostnames.append(hostname)
- return (new_hostnames, credentials)
-
-def parse_hosts_file(hosts_file):
- """
- Parse file with hostnames to connect to. Return list of parsed hostnames.
-
- :param hosts_file: (``file``) File object openned for read.
- It containes hostnames. Each hostname occupies single line.
- :rtype: (``tuple``) A pair of ``(hosts, creds)``, where ``hosts`` is a list
- of string with hostnames and ``creds`` is a dictionary mapping
- ``(username, password)`` to each hostname if supplied.
- """
- hostnames = []
- for line in hosts_file.readlines():
- hostnames.append(line.strip())
- return get_hosts_credentials(hostnames)
-
-def get_default_hostname(port=None):
- """
- Choose default hostname to connect to. If logged as root, this will default
- to localhost, which results in unix socket being used for connection.
- Otherwise use full qualified domain name and hostname will be tried in this
- order. If they are not address-resolvable, '127.0.0.1' is returned.
-
- This function shall be used only if no uri is specified on command line.
-
- :param port: Port of desired service running on host (CIMOM broker).
- This defaults to :py:attr:`DEFAULT_BROKER_SERVICE_NAME`
- :type port: string or int
- """
- if port is None:
- port = DEFAULT_BROKER_SERVICE_NAME
- # Functions used to get hostname. first resolvable result will be used.
- name_getters = []
- if os.getuid() != 0:
- # Use non-'localhost' name only if we'are not logged in as root
- # for it prevents the use of unix socket.
- name_getters.extend([socket.getfqdn, socket.gethostname])
- name_getters.append(lambda: 'localhost')
- for name_func in name_getters:
- try:
- hostname = name_func()
- socket.getaddrinfo(hostname, port)
- break
- except socket.gaierror:
- pass
- else:
- hostname = '127.0.0.1'
- return hostname
diff --git a/lmi/scripts/common/__init__.py b/lmi/scripts/common/__init__.py
deleted file mode 100644
index cda4266..0000000
--- a/lmi/scripts/common/__init__.py
+++ /dev/null
@@ -1,73 +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>
-#
-"""
-Package with client-side python modules and command line utilities.
-"""
-
-import logging
-
-from lmi.shell import LMINamespace
-from lmi.shell import LMIExceptions
-from lmi.scripts.common.configuration import Configuration
-from lmi.scripts.common.lmi_logging import get_logger
-
-LOG = get_logger(__name__)
-
-def get_computer_system(ns):
- """
- Obtain an instance of ``CIM_ComputerSystem`` or its subclass. Preferred
- class name can be configured in configuration file. If such class does
- not exist, a base class (``CIM_ComputerSystem``) is enumerated instead.
- First feasible instance is cached and returned.
-
- :param ns: Namespace object where to look for computer system class.
- :type ns: :py:class:`lmi.shell.LMINamespace`
- :returns: Instance of ``CIM_ComputerSystem``.
- :rtype: :py:class:`lmi.shell.LMIInstance`.
- """
- if not isinstance(ns, LMINamespace):
- raise TypeError("ns must be an instance of LMINamespace")
- if not hasattr(get_computer_system, '_cs_cache'):
- get_computer_system._cs_cache = {}
- ns_path = ns.connection.uri + '/' + ns.name
- if not ns_path in get_computer_system._cs_cache:
- config = Configuration.get_instance()
- try:
- get_computer_system._cs_cache[ns_path] = cs = \
- getattr(ns, config.system_class_name).first_instance()
- except LMIExceptions.LMIClassNotFound:
- LOG().warn('Failed to get instance of %s on host "%s"'
- ' - falling back to CIM_ComputerSystem.',
- config.system_class_name, ns.connection.uri)
- get_computer_system._cs_cache[ns_path] = cs = \
- ns.CIM_ComputerSystem.first_instance_name()
- LOG().debug('Loaded instance of %s:%s for host "%s".',
- ns.name, cs.classname, ns.connection.uri)
- return get_computer_system._cs_cache[ns_path]
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
diff --git a/lmi/scripts/common/configuration.py b/lmi/scripts/common/configuration.py
deleted file mode 100644
index 97ebebe..0000000
--- a/lmi/scripts/common/configuration.py
+++ /dev/null
@@ -1,311 +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 for Configuration class.
-
-Configuration
----------------------
-
-.. autoclass:: Configuration
- :members:
-
-"""
-
-import os
-from lmi.base.BaseConfiguration import BaseConfiguration
-
-LISTER_FORMATS = ['csv', 'table']
-
-#: Default format string to use in stderr handlers.
-DEFAULT_FORMAT_STRING = "%(cseq)s%(levelname_)-8s:%(creset)s %(message)s"
-
-class Configuration(BaseConfiguration):
- """
- Configuration class specific to software providers.
- *OpenLMI* configuration file should reside in: ::
-
- /etc/openlmi/scripts/lmi.conf
-
- :param string user_config_file_path: Path to the user configuration
- options.
- """
-
- USER_CONFIG_FILE_PATH = "~/.lmirc"
- HISTORY_FILE = "~/.lmi_history"
-
- OUTPUT_SILENT = -1
- OUTPUT_WARNING = 0
- OUTPUT_INFO = 1
- OUTPUT_DEBUG = 2
-
- # indexes to LISTER_FORMATS
- LISTER_FORMAT_CSV = 0
- LISTER_FORMAT_TABLE = 1
-
- def __init__(self, user_config_file_path=USER_CONFIG_FILE_PATH, **kwargs):
- self._user_config_file_path = os.path.expanduser(user_config_file_path)
- BaseConfiguration.__init__(self, **kwargs)
- self._verbosity = None
- self._trace = None
- self._verify_server_cert = None
- self._cim_namespace = None
- self._human_friendly = None
- self._lister_format = None
- self._no_headings = None
- self._log_file = None
- self._history_max_length = None
-
- @classmethod
- def provider_prefix(cls):
- return "scripts"
-
- @classmethod
- def default_options(cls):
- """
- :returns: Dictionary of default values.
- :rtype: dictionary
- """
- defaults = BaseConfiguration.default_options().copy()
- # [Main] options
- defaults["CommandNamespace"] = 'lmi.scripts.cmd'
- defaults["Trace"] = "False"
- defaults["Verbosity"] = "0"
- defaults["HistoryMaxLength"] = "4000"
- # [Log] options
- defaults['ConsoleFormat'] = DEFAULT_FORMAT_STRING
- defaults['ConsoleInfoFormat'] = '%(message)s'
- defaults['FileFormat'] = \
- "%(asctime)s:%(levelname)-8s:%(name)s:%(lineno)d - %(message)s"
- defaults['LogToConsole'] = 'True'
- defaults['OutputFile'] = ''
- # [SSL] options
- defaults['VerifyServerCertificate'] = 'True'
- # [Format] options
- defaults['HumanFriendly'] = 'False' # be ugly by default
- defaults['ListerFormat'] = 'table'
- defaults['NoHeadings'] = 'False'
- return defaults
-
- @classmethod
- def mandatory_sections(cls):
- sects = set(BaseConfiguration.mandatory_sections())
- sects.add('Main')
- sects.add('SSL')
- sects.add('Format')
- return list(sects)
-
- def load(self):
- """ Read additional user configuration file if it exists. """
- BaseConfiguration.load(self)
- self.config.read(self._user_config_file_path)
-
- # *************************************************************************
- # [CIM] options
- # *************************************************************************
- @property
- def namespace(self):
- if self._cim_namespace is None:
- return BaseConfiguration.namespace.fget(self)
- return self._cim_namespace
- @namespace.setter
- def namespace(self, namespace):
- if not isinstance(namespace, basestring) and namespace is not None:
- raise TypeError("namespace must be a string")
- self._cim_namespace = namespace
-
- # *************************************************************************
- # [Main] options
- # *************************************************************************
- @property
- def history_file(self):
- """ Path to a file with history of interactive mode. """
- return os.path.expanduser(self.HISTORY_FILE)
-
- @property
- def history_max_length(self):
- """ Maximum number of lines kept in history file. """
- return self.get_safe('Main', 'HistoryMaxLength', int)
-
- @property
- def silent(self):
- """ Whether to suppress all output messages except for errors. """
- return self.verbosity <= self.OUTPUT_SILENT
-
- @property
- def trace(self):
- """ Whether the tracebacks shall be printed upon errors. """
- if self._trace is not None:
- return self._trace
- return self.get_safe('Main', 'Trace', bool)
-
- @trace.setter
- def trace(self, trace):
- """ Allow to override configuration option Trace. """
- if trace is not None:
- trace = bool(trace)
- self._trace = trace
-
- @property
- def verbose(self):
- """ Whether to output more details. """
- return self.verbosity >= self.OUTPUT_INFO
-
- @property
- def verbosity(self):
- """ Return integer indicating verbosity level of output to console. """
- if self._verbosity is None:
- return self.get_safe('Main', 'Verbosity', int)
- return self._verbosity
-
- @verbosity.setter
- def verbosity(self, level):
- """ Allow to set verbosity without modifying configuration values. """
- if not isinstance(level, (long, int)) and level is not None:
- raise TypeError("level must be integer")
- if level is not None:
- if level < self.OUTPUT_SILENT:
- level = self.OUTPUT_SILENT
- elif level > self.OUTPUT_DEBUG:
- level = self.OUTPUT_DEBUG
- self._verbosity = level
-
- # *************************************************************************
- # [Log] options
- # *************************************************************************
- @property
- def log_file(self):
- """ Path to a file, where logging messages shall be written. """
- if self._log_file is None:
- return self.get_safe('Log', 'OutputFile')
- return self._log_file
- @log_file.setter
- def log_file(self, log_file):
- """ Override logging file path. """
- if log_file is not None and not isinstance(log_file, basestring):
- raise TypeError("log_file must be a string")
- self._log_file = log_file
-
- # *************************************************************************
- # [SSL] options
- # *************************************************************************
- @property
- def verify_server_cert(self):
- """
- Return boolean saying, whether the server-side certificate should be
- checked.
- """
- if self._verify_server_cert is None:
- return self.get_safe('SSL', 'VerifyServerCertificate', bool)
- return self._verify_server_cert
- @verify_server_cert.setter
- def verify_server_cert(self, value):
- """ Allows to override configuration option value. """
- if value is not None:
- value = bool(value)
- self._verify_server_cert = value
-
- # *************************************************************************
- # [Format] options
- # *************************************************************************
- @property
- def human_friendly(self):
- """ Whether to print human-friendly values. """
- if self._human_friendly is None:
- return self.get_safe('Format', 'HumanFriendly', bool)
- return self._human_friendly
- @human_friendly.setter
- def human_friendly(self, value):
- """ Allows to override configuration option value. """
- if value is not None:
- value = bool(value)
- self._human_friendly = value
-
- @property
- def lister_format(self):
- """
- Output format used for lister commands. Returns one of
- * LISTER_FORMAT_CSV
- * LISTER_FORMAT_TABLE
-
- :rtype: integer
- """
- if self._lister_format is None:
- value = self.get_safe('Format', 'ListerFormat')
- try:
- return LISTER_FORMATS.index(value.lower())
- except ValueError:
- value = self.default_options()['ListerFormat']
- return LISTER_FORMATS.index(value.lower())
- return self._lister_format
- @lister_format.setter
- def lister_format(self, value):
- """
- Allows to override configuration option.
-
- :param value: One of items from ``LISTER_FORMATS`` array or an index
- to it.
- :type value: integer or string
- """
- if ( value is not None
- and ( not isinstance(value, int)
- or (value < 0 or value >= len(LISTER_FORMATS)))
- and ( not isinstance(value, basestring)
- or value.lower() not in LISTER_FORMATS)) :
- raise TypeError("value must be an integer or one of: %s" %
- LISTER_FORMATS)
- if isinstance(value, basestring):
- value = LISTER_FORMATS.index(value.lower())
- self._lister_format = value
-
- @property
- def no_headings(self):
- """ Whether to print headings of tables. """
- if self._no_headings is None:
- return self.get_safe('Format', 'NoHeadings', bool)
- return self._no_headings
- @no_headings.setter
- def no_headings(self, value):
- """ Allows to override configuration option. """
- if value is not None:
- value = bool(value)
- self._no_headings = value
-
-# There were some path changes in BaseConfiguration after 0.2.0 release.
-# Let's fallback to older variable name when the new one is not present.
-if hasattr(BaseConfiguration, 'CONFIG_DIRECTORY_TEMPLATE_PROVIDER'):
- setattr( Configuration
- , 'CONFIG_FILE_PATH_TEMPLATE_PROVIDER'
- , getattr(BaseConfiguration, 'CONFIG_DIRECTORY_TEMPLATE_PROVIDER')
- + 'lmi.conf')
-else: # fallback
- setattr( Configuration
- , 'CONFIG_FILE_PATH_TEMPLATE'
- , getattr(BaseConfiguration, 'CONFIG_DIRECTORY_TEMPLATE')
- + 'lmi.conf')
diff --git a/lmi/scripts/common/errors.py b/lmi/scripts/common/errors.py
deleted file mode 100644
index 5162cf8..0000000
--- a/lmi/scripts/common/errors.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>
-#
-"""
-Module with predefined exceptions for use in scripts.
-"""
-
-class LmiError(Exception):
- """
- The base Lmi scripts error.
- All the other exceptions inherit from it.
- """
- pass
-
-class LmiFailed(LmiError):
- """
- Raised, when operation on remote host failes.
- It's supposed to be used especially in command libraries.
- """
- pass
-
-class LmiUnsatisfiedDependencies(LmiFailed):
- """
- Raised when no guarded command in
- :py:class:`~.command.select.LmiSelectCommand` can be loaded due to
- unsatisfied requirements.
- """
- def __init__(self, uris):
- LmiFailed.__init__(self, "Profile and class dependencies were not"
- " satisfied for this session (%s)." % ', '.join(uris))
-
-class LmiUnexpectedResult(LmiError):
- """
- Raised, when command's associated function returns something unexpected.
- """
- def __init__(self, command_class, expected, result):
- LmiError.__init__(self,
- 'Got unexpected result from associated function of'
- ' "%s.%s", expected "%s", got: "%s".' %
- (command_class.__module__, command_class.__name__,
- expected, repr(result)))
-
-class LmiInvalidOptions(LmiError):
- """
- Raised in :py:meth:`~.command.endpoint.LmiEndPointCommand.verify_options`
- method if the options given are not valid.
- """
- pass
-
-class LmiCommandNotFound(LmiError):
- """ Raised, when user requests not registered command. """
- def __init__(self, cmd_name):
- LmiError.__init__(self, 'No such command "%s".' % cmd_name)
-
-class LmiNoConnections(LmiError):
- """ Raised, when no connection to remote hosts could be made. """
- pass
-
-class LmiCommandImportError(LmiError):
- """ Exception raised when command can not be imported. """
- def __init__(self, cmd_name, cmd_path, reason):
- LmiError.__init__(self, 'Failed to import command "%s" (%s): %s' % (
- cmd_name, cmd_path, reason))
-
-class LmiCommandError(LmiError):
- """ Generic exception related to command declaration. """
- def __init__(self, module_name, class_name, msg):
- LmiError.__init__(self, 'Wrong declaration of command "%s": %s'
- % ( ".".join([module_name, class_name])
- if module_name else class_name
- , msg))
-
-class LmiCommandInvalidName(LmiCommandError):
- """ Raised, when command gets invalid name. """
- def __init__(self, module_name, class_name, cmd_name):
- LmiCommandError.__init__(self, module_name, class_name,
- 'Invalid command name "%s".' % cmd_name)
-
-class LmiCommandMissingCallable(LmiCommandError):
- """ Raised, when command declaration is missing callable object. """
- def __init__(self, module_name, class_name):
- LmiCommandError.__init__(self, module_name, class_name,
- 'Missing CALLABLE property.')
-
-class LmiCommandInvalidProperty(LmiCommandError):
- """ Raised, when any command property contains unexpected value. """
- pass
-
-class LmiImportCallableFailed(LmiCommandInvalidProperty):
- """ Raised, when callable object of command could not be imported. """
- def __init__(self, module_name, class_name, callable_prop):
- LmiCommandInvalidProperty.__init__(self, module_name, class_name,
- 'Failed to import callable "%s".' % callable_prop)
-
-class LmiCommandInvalidCallable(LmiCommandInvalidProperty):
- """ Raised, when given callback is not callable. """
- def __init__(self, module_name, class_name, msg):
- LmiCommandInvalidProperty.__init__(self, module_name, class_name, msg)
-
-class LmiBadSelectExpression(LmiCommandError):
- """
- Raised, when expression of :py:class:`~.command.select.LmiSelectCommand`
- could not be evaluated.
- """
- def __init__(self, module_name, class_name, expr):
- LmiCommandError.__init__(self, module_name, class_name,
- "Bad select expression: %s" % expr)
-
-class LmiTerminate(Exception):
- """
- Raised to cleanly terminate interavtive shell.
- """
- def __init__(self, exit_code=0):
- Exception.__init__(self, exit_code)
diff --git a/lmi/scripts/common/formatter/__init__.py b/lmi/scripts/common/formatter/__init__.py
deleted file mode 100644
index db7b7e3..0000000
--- a/lmi/scripts/common/formatter/__init__.py
+++ /dev/null
@@ -1,484 +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>
-#
-"""
-Subpackage with formatter classes used to render and output results.
-
-Each formatter has a :py:meth:`Formatter.produce_output` method taking one
-argument which gets rendered and printed to output stream. Each formatter
-expects different argument, please refer to doc string of particular class.
-"""
-
-import itertools
-import locale
-import os
-import pywbem
-
-from lmi.scripts.common import errors
-from lmi.scripts.common.formatter import command as fcmd
-
-def get_terminal_width():
- """
- Get the number of columns of current terminal if attached to it. It
- defaults to 79 characters.
-
- :returns: Number of columns of attached terminal.
- :rtype: integer
- """
- try:
- term_cols = int(os.popen('stty size', 'r').read().split()[1])
- except (IOError, OSError, ValueError):
- term_cols = 79 # fallback
- return term_cols
-
-class Formatter(object):
- """
- Base formatter class.
-
- It produces string representation of given argument and prints it.
-
- This formatter supports following commands:
-
- * :py:class:`~.command.NewHostCommand`.
-
- :param file stream: Output stream.
- :param integer padding: Number of leading spaces to print at each line.
- :param boolean no_headings: If table headings should be omitted.
- """
-
- def __init__(self, stream, padding=0, no_headings=False):
- if not isinstance(padding, (int, long)):
- raise TypeError("padding must be an integer")
- if padding < 0:
- padding = 0
- self.out = stream
- self.padding = padding
- self.no_headings = no_headings
- #: counter of hosts printed
- self.host_counter = 0
- #: counter of tables produced for current host
- self.table_counter = 0
- #: counter of lines producted for current table
- self.line_counter = 0
-
- @property
- def encoding(self):
- """
- Try to determine encoding for output terminal.
-
- :returns: Encoding used to encode unicode strings.
- :rtype: string
- """
- enc = getattr(self.out, 'encoding')
- if not enc:
- enc = locale.getpreferredencoding()
- return enc
-
- def render_value(self, val):
- """
- Rendering function for single value.
-
- :param val: Any value to render.
- :returns: Value converted to its string representation.
- :rtype: str
- """
- if isinstance(val, unicode):
- return val.encode(self.encoding)
- if not isinstance(val, str):
- val = str(val)
- return val
-
- def print_line(self, line, *args, **kwargs):
- """
- Prints single line. Output message is prefixed with ``padding`` spaces,
- formated and printed to output stream.
-
- :param string line: Message to print, it can contain markers for
- other arguments to include according to ``format_spec`` language.
- Please refer to ``Format Specification Mini-Language`` in python
- documentation.
- :param list args: Positional arguments to ``format()`` function of
- ``line`` argument.
- :param dictionary kwargs: Keyword arguments to ``format()`` function.
- """
- line = ' ' * self.padding + line.format(*args, **kwargs)
- self.out.write(line.encode(self.encoding))
- self.out.write("\n")
- self.line_counter += 1
-
- def print_host(self, hostname):
- """
- Prints header for new host.
-
- :param string hostname: Hostname to print.
- """
- if ( self.host_counter > 0
- or self.table_counter > 0
- or self.line_counter > 0):
- self.out.write("\n")
- term_width = get_terminal_width()
- self.out.write("="*term_width + "\n")
- self.out.write("Host: %s\n" % hostname)
- self.out.write("="*term_width + "\n")
- self.host_counter += 1
- self.table_counter = 0
- self.line_counter = 0
-
- def produce_output(self, data):
- """
- Render and print given data.
-
- Data can be also instance of
- :py:class:`~.command.FormatterCommand`, see
- documentation of this class for list of
- allowed commands.
-
- This shall be overridden by subclasses.
-
- :param data: Any value to print. Subclasses may specify their
- requirements for this argument. It can be also am instance of
- :py:class:`~.command.FormatterCommand`.
- """
- self.print_line(str(data))
- self.line_counter += 1
-
-class ListFormatter(Formatter):
- """
- Base formatter taking care of list of items. It renders input data in a
- form of table with mandatory column names at the beginning followed by
- items, one occupying single line (row).
-
- This formatter supports following commands:
- * :py:class:`~.command.NewHostCommand`
- * :py:class:`~.command.NewTableCommand`
- * :py:class:`~.command.NewTableHeaderCommand`
-
- The command must be provided as content of one row. This row is then not
- printed and the command is executed.
-
- This class should be subclassed to provide nice output.
- """
- def __init__(self, stream, padding=0, no_headings=False):
- super(ListFormatter, self).__init__(stream, padding, no_headings)
- self.column_names = None
-
- def print_text_row(self, row):
- """
- Print data row without any header.
-
- :param tuple row: Data to print.
- """
- self.out.write(" "*self.padding + self.render_value(row) + "\n")
- self.line_counter += 1
-
- def print_row(self, data):
- """
- Print data row. Optionaly print header, if requested.
-
- :param tuple data: Data to print.
- """
- if self.line_counter == 0 and not self.no_headings:
- self.print_header()
- self.print_text_row(data)
-
- def print_table_title(self, title):
- """
- Prints title of next tale.
-
- :param string title: Title to print.
- """
- if self.table_counter > 0 or self.line_counter > 0:
- self.out.write('\n')
- self.out.write("%s:\n" % title)
- self.table_counter += 1
- self.line_counter = 0
-
- def print_header(self):
- """ Print table header. """
- if self.no_headings:
- return
- if self.column_names:
- self.print_text_row(self.column_names)
-
- def produce_output(self, rows):
- """
- Prints list of rows.
-
- There can be a :py:class:`~.command.FormatterCommand` instance instead
- of a row. See documentation of this class for list of allowed commands.
-
- :param rows: List of rows to print.
- :type rows: list, generator or :py:class:`.command.FormatterCommand`
- """
- for row in rows:
- if isinstance(row, fcmd.NewHostCommand):
- self.print_host(row.hostname)
- elif isinstance(row, fcmd.NewTableCommand):
- self.print_table_title(row.title)
- elif isinstance(row, fcmd.NewTableHeaderCommand):
- self.column_names = row.columns
- else:
- self.print_row(row)
-
-class TableFormatter(ListFormatter):
- """
- Print nice human-readable table to terminal.
-
- To print the table nicely aligned, the whole table must be populated first.
- Therefore this formatter stores all rows locally and does not print
- them until the table is complete. Column sizes are computed afterwards
- and the table is printed at once.
-
- This formatter supports following commands:
- * :py:class:`~.command.NewHostCommand`
- * :py:class:`~.command.NewTableCommand`
- * :py:class:`~.command.NewTableHeaderCommand`
-
- The command must be provided as content of one row. This row is then not
- printed and the command is executed.
- """
- def __init__(self, stream, padding=0, no_headings=False):
- super(TableFormatter, self).__init__(stream, padding, no_headings)
- self.stash = []
-
- def print_text_row(self, row, column_sizes):
- for i in xrange(len(row)):
- size = column_sizes[i]
- # Convert to unicode to compute correct length of utf-8 strings
- # (e.g. with fancy trees with utf-8 graphics).
- item = ( unicode(row[i]) if not isinstance(row[i], str)
- else row[i].decode(self.encoding))
- if i < len(row) - 1:
- item = item.ljust(size)
- self.out.write(self.render_value(item))
- if i < len(row) - 1:
- self.out.write(" ")
- self.out.write("\n")
- self.line_counter += 1
-
- def print_stash(self):
- # Compute column sizes
- column_sizes = []
- rows = iter(self.stash)
- try:
- if self.column_names is None:
- row = rows.next()
- else:
- row = self.column_names
- for i in xrange(len(row)):
- column_sizes.append(len(row[i]))
- except StopIteration:
- pass # empty stash
-
- for row in rows:
- for i in xrange(len(row)):
- row_length = len(
- unicode(row[i]) if not isinstance(row[i], str)
- else row[i].decode(self.encoding))
- if column_sizes[i] < row_length:
- column_sizes[i] = row_length
-
- # print headers
- if not self.no_headings and self.column_names:
- self.print_text_row(self.column_names, column_sizes)
- # print stashed rows
- for row in self.stash:
- self.print_text_row(row, column_sizes)
- self.stash = []
-
- def print_row(self, data):
- """
- Print data row.
-
- :param tuple data: Data to print.
- """
- self.stash.append(data)
-
- def print_host(self, hostname):
- """
- Prints header for new host.
-
- :param string hostname: Hostname to print.
- """
- if len(self.stash):
- # without a check, this would print headers for empty stash
- self.print_stash()
- super(TableFormatter, self).print_host(hostname)
-
- def print_table_title(self, title):
- """
- Prints title of next tale.
-
- :param string title: Title to print.
- """
- if len(self.stash):
- # without a check, this would print headers for empty stash
- self.print_stash()
- if self.table_counter > 0 or self.line_counter > 0:
- self.out.write('\n')
- self.out.write("%s:\n" % title)
- self.table_counter += 1
- self.line_counter = 0
-
- def produce_output(self, rows):
- """
- Prints list of rows.
-
- There can be :py:class:`~.command.FormatterCommand` instance instead of
- a row. See documentation of this class for list of allowed commands.
-
- :param rows: List of rows to print.
- :type rows: list or generator
- """
- super(TableFormatter, self).produce_output(rows)
- self.print_stash()
-
-class CsvFormatter(ListFormatter):
- """
- Renders data in a csv (Comma-separated values) format.
-
- This formatter supports following commands:
- * :py:class:`~.command.NewHostCommand`
- * :py:class:`~.command.NewTableCommand`
- * :py:class:`~.command.NewTableHeaderCommand`
- """
-
- def render_value(self, val):
- if isinstance(val, basestring):
- if isinstance(val, unicode):
- val.encode(self.encoding)
- val = '"%s"' % val.replace('"', '""')
- elif val is None:
- val = ''
- else:
- val = str(val)
- return val
-
- def print_text_row(self, row):
- self.print_line(",".join(self.render_value(v) for v in row))
- self.line_counter += 1
-
-class SingleFormatter(Formatter):
- """
- Meant to render and print attributes of single object. Attributes are
- rendered as a list of assignments of values to variables (attribute names).
-
- This formatter supports following commands:
- * :py:class:`~.command.NewHostCommand`
- """
-
- def produce_output(self, data):
- """
- Render and print attributes of single item.
-
- There can be a :py:class:`~.command.FormatterCommand` instance instead
- of a data. See documentation of this class for list of allowed
- commands.
-
- :param data: Is either a pair of property names with list of values or
- a dictionary with property names as keys. Using the pair allows to
- order the data the way it should be printing. In the latter case
- the properties will be sorted by the property names.
- :type data: tuple or dict
- """
- if isinstance(data, fcmd.NewHostCommand):
- self.print_host(data.hostname)
- return
-
- if not isinstance(data, (tuple, dict)):
- raise ValueError("data must be tuple or dict")
-
- if isinstance(data, tuple):
- if not len(data) == 2:
- raise ValueError(
- "data must contain: (list of columns, list of rows)")
- dataiter = itertools.izip(data[0], data[1])
- else:
- dataiter = itertools.imap(
- lambda k: (k, self.render_value(data[k])),
- sorted(data.keys()))
- for name, value in dataiter:
- self.print_line("{0}={1}", name, value)
- self.line_counter += 1
-
-class ShellFormatter(SingleFormatter):
- """
- Specialization of :py:class:`SingleFormatter` having its output executable
- as a shell script.
-
- This formatter supports following commands:
- * :py:class:`~.command.NewHostCommand`
- """
-
- def render_value(self, val):
- if isinstance(val, basestring):
- if isinstance(val, unicode):
- val.encode(self.encoding)
- val = "'%s'" % val.replace("'", "\\'")
- elif val is None:
- val = ''
- else:
- val = str(val)
- return val
-
-class ErrorFormatter(ListFormatter):
- """
- Render error strings for particular host. Supported commands:
- * :py:class:`~.command.NewHostCommand`
- """
- def __init__(self, stream, padding=4):
- super(ErrorFormatter, self).__init__(stream, padding)
-
- def print_row(self, data):
- if isinstance(data, Exception):
- if isinstance(data, pywbem.CIMError):
- self.print_text_row("%s: %s" % (data.args[1], data.message))
- elif not isinstance(data, errors.LmiFailed):
- self.print_text_row("(%s) %s" % (data.__class__.__name__,
- str(data)))
- else:
- self.print_text_row(data)
- else:
- self.print_text_row(data)
-
- def print_host(self, hostname):
- self.out.write("host %s\n" % hostname)
- self.host_counter += 1
- self.table_counter = 0
- self.line_counter = 0
-
- def produce_output(self, rows):
- for row in rows:
- if isinstance(row, fcmd.NewHostCommand):
- self.print_host(row.hostname)
- elif isinstance(row, fcmd.NewTableCommand):
- self.print_table_title(row.title)
- else:
- self.print_row(row)
-
diff --git a/lmi/scripts/common/formatter/command.py b/lmi/scripts/common/formatter/command.py
deleted file mode 100644
index 78830c6..0000000
--- a/lmi/scripts/common/formatter/command.py
+++ /dev/null
@@ -1,71 +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 used to control formatters from inside of command
-execution functions.
-"""
-
-class FormatterCommand(object):
- """
- Base class for formatter commands.
- """
- pass
-
-class NewHostCommand(FormatterCommand):
- """
- Command for formatter to finish current table (if any), print header for
- new host and (if there are any data) print table header.
-
- :param string hostname: Name of host appearing at the front of new table.
- """
- def __init__(self, hostname):
- self.hostname = hostname
-
-class NewTableCommand(FormatterCommand):
- """
- Command for formatter to finish current table (if any), print the **title**
- and (if there are any data) print table header.
-
- :param string title: Optional title for new table.
- """
- def __init__(self, title=None):
- self.title = title
-
-class NewTableHeaderCommand(FormatterCommand):
- """
- Command for formatter to finish current table (if any), store new table
- header and (if there are any data) print the table header.
- The table header will be printed in all subsequent tables, until
- new instance of this class arrives.
-
- :param tuple columns: Array of column names.
- """
- def __init__(self, columns=None):
- self.columns = columns
diff --git a/lmi/scripts/common/lmi_logging.py b/lmi/scripts/common/lmi_logging.py
deleted file mode 100644
index a6ff0e1..0000000
--- a/lmi/scripts/common/lmi_logging.py
+++ /dev/null
@@ -1,256 +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>
-#
-"""
-Utilities for logging framework.
-"""
-import logging
-import sys
-
-from lmi.scripts.common import configuration
-from lmi.scripts.common import errors
-
-TERM_COLOR_NAMES = (
- 'black',
- 'red',
- 'green',
- 'yellow',
- 'blue',
- 'magenta',
- 'cyan',
- 'white',
- )
-
-# Following are terminal color codes of normal mode.
-CN_BLACK = 0
-CN_RED = 1
-CN_GREEN = 2
-CN_YELLOW = 3
-CN_BLUE = 4
-CN_MAGENTA = 5
-CN_CYAN = 6
-CN_WHITE = 7
-# Following are terminal color codes of bright mode.
-CB_BLACK = 8
-CB_RED = 9
-CB_GREEN = 10
-CB_YELLOW = 11
-CB_BLUE = 12
-CB_MAGENTA = 13
-CB_CYAN = 14
-CB_WHITE = 15
-
-WARNING_COLOR = CB_YELLOW
-ERROR_COLOR = CB_RED
-CRITICAL_COLOR = CB_MAGENTA
-
-#: Dictionary assigning color code to logging level.
-LOG_LEVEL_2_COLOR = {
- logging.WARNING : WARNING_COLOR,
- logging.ERROR : ERROR_COLOR,
- logging.CRITICAL: CRITICAL_COLOR
-}
-
-class LogRecord(logging.LogRecord):
- """
- Overrides :py:class:`logging.LogRecord`. It adds new attributes:
-
- * `levelname_` - Name of level in lowercase.
- * `cseq` - Escape sequence for terminal used to set color
- assigned to particular log level.
- * `creset` - Escape sequence for terminal used to reset foreground
- color.
-
- These can be used in format strings initializing logging formatters.
-
- Accepts the same arguments as base class.
- """
-
- def __init__(self, name, level, *args, **kwargs):
- use_colors = kwargs.pop('use_colors', True)
- logging.LogRecord.__init__(self, name, level, *args, **kwargs)
- self.levelname_ = self.levelname.lower()
- if use_colors and level >= logging.WARNING:
- if level >= logging.CRITICAL:
- color = LOG_LEVEL_2_COLOR[logging.CRITICAL]
- elif level >= logging.ERROR:
- color = LOG_LEVEL_2_COLOR[logging.ERROR]
- else:
- color = LOG_LEVEL_2_COLOR[logging.WARNING]
- self.cseq = get_color_sequence(color)
- self.creset = "\x1b[39m"
- else:
- self.cseq = self.creset = ''
-
-class ScriptsLogger(logging.getLoggerClass()):
-
- #: Boolean saying whether the color sequences for log messages shall be
- #: generated.
- USE_COLORS = True
-
- def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None,
- extra=None):
- """
- Overriden method that just changes the *LogRecord* class to our
- predefined and ensures that exception's traceback is printed once at
- most.
- """
- if exc_info:
- err = exc_info[1]
- if getattr(err, '_traceback_logged', False):
- # do not print traceback second time
- exc_info = None
- elif self.isEnabledFor(level):
- try:
- err._traceback_logged = True
- except AttributeError:
- pass
- rv = LogRecord(name, level, fn, lno, msg, args, exc_info, func,
- use_colors=self.USE_COLORS)
- if extra is not None:
- for key in extra:
- if (key in ["message", "asctime"]) or (key in rv.__dict__):
- raise KeyError("Attempt to overwrite %r in LogRecord" % key)
- rv.__dict__[key] = extra[key]
- return rv
-
- def exception(self, msg, *args, **kwargs):
- lmi_config = configuration.Configuration.get_instance()
- exc_info = sys.exc_info()
- err = exc_info[1]
- if ( lmi_config.trace
- and ( not isinstance(err, errors.LmiError)
- or ( not getattr(err, '_traceback_logged', False)
- and lmi_config.verbosity >= lmi_config.OUTPUT_DEBUG))):
- kwargs['exc_info'] = exc_info
- else:
- kwargs.pop('exc_info', None)
- self.error(msg, *args, **kwargs)
-
-class LevelDispatchingFormatter(object):
- """
- Formatter class for logging module. It allows to predefine different format
- string used for some level ranges.
-
- :param dict formatters: Mapping of module names to *format*.
- It is a dictionary of following format: ::
-
- { max_level1 : format1
- , max_level2 : format2
- , ... }
-
- *format* in parameters description can be either ``string`` or another
- formatter object.
-
- For example if you want to have format3 used for *ERROR* and *CRITICAL*
- levels, *format2* for *INFO* and *format1* for anything else, your
- dictionary will look like this: ::
-
- { logging.INFO - 1 : format1
- , logging.INFO : format2 }
-
- And the *default* value should have *format3* assigned.
-
- :param default: Default *format* to use. This format is used for all levels
- higher than the maximum of *formatters*' keys.
- """
- def __init__(self, formatters, default=configuration.DEFAULT_FORMAT_STRING,
- datefmt=None):
- for k, formatter in formatters.items():
- if isinstance(formatter, basestring):
- formatters[k] = logging.Formatter(formatter, datefmt=datefmt)
- #: This a a tuple of pairs sorted by the first item in descending
- #: order (highest priority ordered first).
- self._formatters = tuple(sorted(formatters.items(),
- key=lambda t: -t[0]))
- if isinstance(default, basestring):
- default = logging.Formatter(default, datefmt=datefmt)
- self._default_formatter = default
-
- def format(self, record):
- """
- Interface for logging module.
- """
- record.levelname_ = record.levelname.lower()
- formatter = self._default_formatter
- for level, fmt in self._formatters:
- if level < record.levelno:
- break
- formatter = fmt
- try:
- return formatter.format(record)
- except KeyError:
- # in some modules or libraries it may happen that logger is
- # initialized before our logger class is set as default
- record.cseq = record.creset = ''
- return formatter.format(record)
-
-def get_logger(module_name):
- """
- Convenience function for getting callable returning logger for particular
- module name. It's supposed to be used at module's level to assign its
- result to global variable like this: ::
-
- from lmi.scripts import common
-
- LOG = common.get_logger(__name__)
-
- This can be used in module's functions and classes like this: ::
-
- def module_function(param):
- LOG().debug("This is debug statement logging param: %s", param)
-
- Thanks to ``LOG`` being a callable, it always returns valid logger object
- with current configuration, which may change overtime.
-
- :param string module_name: Absolute dotted module path.
- :rtype: :py:class:`logging.Logger`
- """
- def _logger():
- """ Callable used to obtain current logger object. """
- return logging.getLogger(module_name)
- return _logger
-
-def get_color_sequence(color_code):
- """
- Computer color sequence for particular color code.
-
- :returns: Escape sequence for terminal used to set foreground color.
- :rtype: str
- """
- if color_code <= 7:
- return "\x1b[%dm" % (30 + color_code)
- return "\x1b[38;5;%dm" % color_code
-
-def setup_logger(use_colors=True):
- """
- This needs to be called before any logging takes place.
- """
- ScriptsLogger.USE_COLORS = use_colors
- logging.setLoggerClass(ScriptsLogger)
diff --git a/lmi/scripts/common/session.py b/lmi/scripts/common/session.py
deleted file mode 100644
index b063d0a..0000000
--- a/lmi/scripts/common/session.py
+++ /dev/null
@@ -1,190 +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 for session object representing all connection to remote hosts.
-"""
-
-from collections import defaultdict
-
-from lmi.scripts.common import errors
-from lmi.scripts.common import get_logger
-from lmi.scripts.common.util import FilteredDict
-from lmi.shell.LMIConnection import connect
-
-LOG = get_logger(__name__)
-
-class Session(object):
- """
- Session object keeps connection objects to remote hosts. Their are
- associated with particular hostnames. It also caches credentials for them.
- Connections are made as they are needed. When credentials are missing
- for connection to be made, the user is asked to supply them from
- standard input.
-
- :param app: Instance of main application.
- :param list hosts: List of hostname strings.
- :param dictionary credentials: Mapping assigning a pair
- ``(user, password)`` to each hostname.
- :param boolean same_credentials: Use the same credentials for all
- hosts in session. The first credentials given will be used.
- """
-
- def __init__(self, app, hosts, credentials=None, same_credentials=False):
- self._app = app
- self._connections = {h: None for h in hosts}
- # { hostname : (username, password, verified), ... }
- # where verified is a flag saying, whether these credentials
- # were successfuly used for logging in
- self._credentials = defaultdict(lambda: ('', '', False))
- self._same_credentials = same_credentials
- if credentials is not None:
- if not isinstance(credentials, dict):
- raise TypeError("credentials must be a dictionary")
- for hostname, creds in credentials.items():
- credentials[hostname] = (creds[0], creds[1], False)
- self._credentials.update(credentials)
-
- def __getitem__(self, hostname):
- """
- :rtype: (``LMIConnection``) Connection object to remote host.
- ``None`` if connection can not be made.
- """
- if self._connections[hostname] is None:
- try:
- self._connections[hostname] = self._connect(
- hostname, interactive=True)
- except Exception as exc:
- LOG().error('Failed to make a connection to "%s": %s',
- hostname, exc)
- return self._connections[hostname]
-
- def __len__(self):
- """ Get the number of hostnames in session. """
- return len(self._connections)
-
- def __contains__(self, uri):
- return uri in self._connections
-
- def __iter__(self):
- """ Yields connection objects. """
- successful_connections = 0
- for hostname in self._connections:
- connection = self[hostname]
- if connection is not None:
- yield connection
- successful_connections += 1
- if successful_connections == 0:
- raise errors.LmiNoConnections('No successful connection made.')
-
- def _connect(self, hostname, interactive=False):
- """
- Makes the connection to host.
-
- :param string hostname: Name of host.
- :param boolean interactive: Whether we can interact with user
- and expect a reply from him.
- :returns: Connection to remote host or ``None``.
- :rtype: :py:class:`lmi.shell.LMIConnection` or ``None``
- """
- username, password = self.get_credentials(hostname)
- kwargs = {
- 'verify_server_cert' : self._app.config.verify_server_cert,
- 'interactive' : interactive
- }
- if len(self._connections) > 1:
- kwargs['prompt_prefix'] = '[%s] ' % hostname
- connection = connect(hostname, username, password, **kwargs)
- if connection is not None:
- tp = connection._client._cliconn.creds
- if tp is None:
- tp = ('', '')
- self._credentials[hostname] = (tp[0], tp[1], True)
- else:
- LOG().error('Failed to connect to host "%s".', hostname)
- return connection
-
- @property
- def hostnames(self):
- """
- List of hostnames in session.
-
- :rtype: list
- """
- return self._connections.keys()
-
- def get_credentials(self, hostname):
- """
- :param string hostname: Name of host to get credentials for.
- :returns: Pair of ``(username, password)`` for given hostname. If no
- credentials were given for this host, ``('', '')`` is returned.
- :rtype: tuple
- """
- username, password, verified = self._credentials[hostname]
- if ( not verified
- and (not username or not password)
- and self._same_credentials):
- for tp in self._credentials.values():
- if tp[2]:
- username, password = tp[0], tp[1]
- break
- return username, password
-
- def get_unconnected(self):
- """
- :returns: List of hostnames, which do not have associated connection
- yet.
- :rtype: list
- """
- return [h for h, c in self._connections.items() if c is None]
-
-class SessionProxy(Session):
- """
- Behaves like a session. But it just encapsulates other session object and
- provides access to a subset of its items.
-
- :param session: Session object or even another session proxy.
- :param list uris: Subset of uris in encapsulated session object.
- """
-
- def __init__(self, session, uris):
- uris = set(uris)
- if not all(isinstance(uri, basestring) for uri in uris):
- raise ValueError("uris must be iterable of uris")
- for uri in uris:
- if not uri in session:
- raise ValueError('uri "%s" needs to belong to given session'
- % uri)
- Session.__init__(self, session._app, uris, session._credentials,
- session._same_credentials)
- self._origin = session
- self._connections = FilteredDict(uris, session._connections)
- # let the credentials propagage to original session
- self._credentials = session._credentials
-
diff --git a/lmi/scripts/common/util.py b/lmi/scripts/common/util.py
deleted file mode 100644
index 4ee2161..0000000
--- a/lmi/scripts/common/util.py
+++ /dev/null
@@ -1,146 +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>
-#
-"""
-Various utilities for LMI Scripts.
-"""
-
-class FilteredDict(dict):
- """
- Dictionary-like collection that wraps some other dictionary and provides
- limited access to its keys and values. It permits to get, delete and set
- items specified in advance.
-
- .. note::
- Please use only the methods overriden. This class does not guarantee
- 100% API compliance. Not overriden methods won't work properly.
-
- :param list key_filter: Set of keys that can be get, set or deleted.
- For other keys, :py:class:`KeyError` will be raised.
- :param dictionary original: Original dictionary containing not only
- keys in *key_filter* but others as well. All modifying operations
- operate also on this dictionary. But only those keys in *key_filter*
- can be affected by them.
- """
-
- def __init__(self, key_filter, original=None):
- dict.__init__(self)
- if original is not None and not isinstance(original, dict):
- raise TypeError("original needs to be a dictionary")
- if original is None:
- original = dict()
- self._original = original
- self._keys = frozenset(key_filter)
-
- def __contains__(self, key):
- return key in self._keys and key in self._original
-
- def __delitem__(self, key):
- if not key in self._keys:
- raise KeyError(repr(key))
- del self._original[key]
-
- def clear(self):
- for key in self._keys:
- self._original.pop(key, None)
-
- def copy(self):
- return FilteredDict(self._keys, self._original)
-
- def iterkeys(self):
- for key in self._keys:
- yield key
-
- def __getitem__(self, key):
- if not key in self._keys:
- raise KeyError(repr(key))
- return self._original[key]
-
- def __iter__(self):
- for key in self._keys:
- if key in self._original:
- yield key
-
- def __eq__(self, other):
- return ( isinstance(other, FilteredDict)
- and self._original == other._original
- and self._keys == other._keys)
-
- def __lt__(self, other):
- if isinstance(other, dict):
- return { k: v for k, v in self._original.items()
- if k in self._keys} < other
- if not isinstance(other, FilteredDict):
- raise TypeError("Can not compare FilteredDict to objects"
- " of other types!")
- return self._original <= other._original and self._keys <= other._keys
-
- def __len__(self):
- return len(self.keys())
-
- def __setitem__(self, key, value):
- if not key in self._keys:
- raise KeyError(repr(key))
- self._original[key] = value
-
- def keys(self):
- return [k for k in self._keys if k in self._original]
-
- def values(self):
- return [self._original[k] for k in self.keys() if k in self._original]
-
- def items(self):
- return [(k, self._original[k]) for k in self.keys()]
-
- def iteritems(self):
- return iter(self.items())
-
- def pop(self, key, *args):
- ret = self[key]
- del self[key]
- return ret
-
- def popitem(self):
- for key in self._keys:
- if key in self._original:
- return self.pop(key)
- raise KeyError("FilterDict is empty!")
-
- def update(self, *args, **kwargs):
- if len(args) > 1:
- raise TypeError('Expected just one positional argument!')
- if args and callable(getattr(args[0], 'keys', None)):
- for key in args[0].keys():
- self[key] = args[0][key]
- elif args:
- for key, value in args[0]:
- self[key] = value
- for key, value in kwargs.items():
- self[key] = value
-
diff --git a/lmi/scripts/common/versioncheck/__init__.py b/lmi/scripts/common/versioncheck/__init__.py
deleted file mode 100644
index c7e595f..0000000
--- a/lmi/scripts/common/versioncheck/__init__.py
+++ /dev/null
@@ -1,146 +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>
-#
-"""
-Package with utilities for checking availability of profiles or CIM classes.
-Version requirements can also be specified.
-"""
-
-import functools
-from pyparsing import ParseException
-
-from lmi.scripts.common import Configuration
-from lmi.scripts.common import errors
-from lmi.scripts.common.versioncheck import parser
-
-def cmp_profiles(fst, snd):
- """
- Compare two profiles by their version.
-
- :returns:
- * -1 if the *fst* profile has lower version than *snd*
- * 0 if their versions are equal
- * 1 otherwise
- :rtype: int
- """
- fstver = fst.RegisteredVersion
- sndver = snd.RegisteredVersion
- if fstver == sndver:
- return 0
- return -1 if parser.cmp_version(fstver, sndver) else 1
-
-def get_profile_version(conn, name, cache=None):
- """
- Get version of registered profile on particular broker. Queries
- ``CIM_RegisteredProfile`` and ``CIM_RegisteredSubProfile``. The latter
- comes in question only when ``CIM_RegisteredProfile`` does not yield any
- matching result.
-
- :param conn: Connection object.
- :param string name: Name of the profile which must match value of *RegisteredName*
- property.
- :param dictionary cache: Optional cache where the result will be stored for
- later use. This greatly speeds up evaluation of several expressions refering
- to same profiles or classes.
- :returns: Version of matching profile found. If there were more of them,
- the highest version will be returned. ``None`` will be returned when no matching
- profile or subprofile is found.
- :rtype: string
- """
- if cache and name in cache:
- return cache[(conn.uri, name)]
- insts = conn.root.interop.wql('SELECT * FROM CIM_RegisteredProfile'
- ' WHERE RegisteredName=\"%s\"' % name)
- regular = set(i for i in insts if i.classname.endswith('RegisteredProfile'))
- if regular: # select instances of PG_RegisteredProfile if available
- insts = regular
- else: # otherwise fallback to PG_RegisteredSubProfile instances
- insts = set(i for i in insts if i not in regular)
- if not insts:
- ret = None
- else:
- ret = sorted(insts, cmp=cmp_profiles)[-1].RegisteredVersion
- if cache is not None:
- cache[(conn.uri, name)] = ret
- return ret
-
-def get_class_version(conn, name, namespace=None, cache=None):
- """
- Query broker for version of particular CIM class. Version is stored in
- ``Version`` qualifier of particular CIM class.
-
- :param conn: Connection object.
- :param string name: Name of class to query.
- :param string namespace: Optional CIM namespace. Defaults to configured namespace.
- :param dictionary cache: Optional cache used to speed up expression prrocessing.
- :returns: Version of CIM matching class. Empty string if class is registered but
- is missing ``Version`` qualifier and ``None`` if it is not registered.
- :rtype: string
- """
- if namespace is None:
- namespace = Configuration.get_instance().namespace
- if cache and (namespace, name) in cache:
- return cache[(conn.uri, namespace, name)]
- ns = conn.get_namespace(namespace)
- cls = getattr(ns, name, None)
- if not cls:
- ret = None
- else:
- quals = cls.wrapped_object.qualifiers
- if 'Version' not in quals:
- ret = ''
- else:
- ret = quals['Version'].value
- if cache is not None:
- cache[(conn.uri, namespace, name)] = ret
- return ret
-
-def eval_respl(expr, conn, namespace=None, cache=None):
- """
- Evaluate LMIReSpL expression on particular broker.
-
- :param string expr: Expression to evaluate.
- :param conn: Connection object.
- :param string namespace: Optional CIM namespace where CIM classes will be
- searched.
- :param dictionary cache: Optional cache speeding up evaluation.
- :returns: ``True`` if requirements in expression are satisfied.
- :rtype: boolean
- """
- if namespace is None:
- namespace = Configuration.get_instance().namespace
- stack = []
- pvget = functools.partial(get_profile_version, conn, cache=cache)
- cvget = functools.partial(get_class_version, conn,
- namespace=namespace, cache=cache)
- pr = parser.bnf_parser(stack, pvget, cvget)
- pr.parseString(expr, parseAll=True)
- # Now evaluate starting non-terminal created on stack.
- return stack[0]()
-
diff --git a/lmi/scripts/common/versioncheck/parser.py b/lmi/scripts/common/versioncheck/parser.py
deleted file mode 100644
index b5f9116..0000000
--- a/lmi/scripts/common/versioncheck/parser.py
+++ /dev/null
@@ -1,521 +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>
-#
-"""
-Parser for mini-language specifying profile and class requirements. We call
-the language LMIReSpL (openLMI Requirement Specification Language).
-
-The only thing designed for use outside this module is :py:func:`bnf_parser`.
-
-Language is generated by BNF grammer which served as a model for parser.
-
-Formal representation of BNF grammer is following: ::
-
- expr ::= term [ op expr ]*
- term ::= '!'? req
- req ::= profile_cond | clsreq_cond | '(' expr ')'
- profile_cond ::= 'profile'? [ profile | profile_quot ] cond?
- clsreq_cond ::= 'class' [ clsname | clsname_quot] cond?
- profile_quot ::= '"' /\w+[ +.a-zA-Z0-9_-]*/ '"'
- profile ::= /\w+[+.a-zA-Z_-]*/
- clsname_quot ::= '"' clsname '"'
- clsname ::= /[a-zA-Z]+_[a-zA-Z][a-zA-Z0-9_]*/
- cond ::= cmpop version
- cmpop ::= /(<|=|>|!)=|<|>/
- version ::= /[0-9]+(\.[0-9]+)*/
- op ::= '&' | '|'
-
-String surrounded by quotes is a literal. String enclosed with slashes is a
-regular expression. Square brackets encloses a group of words and limit
-the scope of some operation (like iteration).
-"""
-import abc
-import operator
-from pyparsing import Literal, Combine, Optional, ZeroOrMore, \
- Forward, Regex, Keyword, FollowedBy, LineEnd, ParseException
-
-#: Dictionary mapping supported comparison operators to a pair. First item is a
-#: function making the comparison and the second can be of two values (``all``
-#: or ``any``). Former sayes that each part of first version string must be in
-#: relation to corresponding part of second version string in order to satisfy
-#: the condition. The latter causes the comparison to end on first satisfied
-#: part.
-OP_MAP = {
- '==' : (operator.eq, all),
- '<=' : (operator.le, all),
- '>=' : (operator.ge, all),
- '!=' : (operator.ne, any),
- '>' : (operator.gt, any),
- '<' : (operator.lt, any)
-}
-
-def cmp_version(fst, snd, opsign='<'):
- """
- Compare two version specifications. Each version string shall contain
- digits delimited with dots. Empty string is also valid version. It will be
- replaced with -1.
-
- :param str fst: First version string.
- :param str snd: Second version string.
- :param str opsign: Sign denoting operation to be used. Supported signs
- are present in :py:attr:`OP_MAP`.
- :returns: ``True`` if the relation denoted by particular operation exists
- between two operands.
- :rtype: boolean
- """
- def splitver(ver):
- """ Converts version string to a tuple of integers. """
- return tuple(int(p) if p else -1 for p in ver.split('.'))
- aparts = splitver(fst)
- bparts = splitver(snd)
- op, which = OP_MAP[opsign]
- if which is all:
- for ap, bp in zip(aparts, bparts):
- if not op(ap, bp):
- return False
- else:
- for ap, bp in zip(aparts, bparts):
- if op(ap, bp):
- return True
- if ap != bp:
- return False
- return op(len(aparts), len(bparts))
-
-class SemanticGroup(object):
- """
- Base class for non-terminals. Just a minimal set of non-terminals is
- represented by objects the rest is represented by strings.
-
- All subclasses need to define their own :py:meth:`evaluate` method. The
- parser builds a tree of these non-terminals with single non-terminal being
- a root node. This node's *evaluate* method returns a boolean saying whether
- the condition is satisfied. Root node is always an object of
- :py:class:`Expr`.
- """
-
- __metaclass__ = abc.ABCMeta
-
- def __call__(self):
- return self.evaluate()
-
- @abc.abstractmethod
- def evaluate(self):
- """
- :returns: ``True`` if the sub-condition represented by this non-terminal
- is satisfied.
- :rtype: boolean
- """
- pass
-
-class Expr(SemanticGroup):
- """
- Initial non-terminal. Object of this class (or one of its subclasses) is a
- result of parsing.
-
- :param term: An object of :py:class:`Term` non-terminal.
- """
-
- def __init__(self, term):
- assert isinstance(term, Term)
- self.fst = term
-
- def evaluate(self):
- return self.fst()
-
- def __str__(self):
- return str(self.fst)
-
-class And(Expr):
- """
- Represents logical *AND* of two expressions. Short-circuit evaluation is
- being exploited here.
-
- :param fst: An object of :py:class:`Term` non-terminal.
- :param snd: An object of :py:class:`Term` non-terminal.
- """
-
- def __init__(self, fst, snd):
- assert isinstance(snd, (Term, Expr))
- Expr.__init__(self, fst)
- self.snd = snd
-
- def evaluate(self):
- if self.fst():
- return self.snd()
- return False
-
- def __str__(self):
- return "%s & %s" % (self.fst, self.snd)
-
-class Or(Expr):
- """
- Represents logical *OR* of two expressions. Short-circuit evaluation is being
- exploited here.
-
- :param fst: An object of :py:class:`Term` non-terminal.
- :param snd: An object of :py:class:`Term` non-terminal.
- """
-
- def __init__(self, fst, snd):
- assert isinstance(snd, (Term, Expr))
- Expr.__init__(self, fst)
- self.snd = snd
-
- def evaluate(self):
- if self.fst():
- return True
- return self.snd()
-
- def __str__(self):
- return "%s | %s" % (self.fst, self.snd)
-
-class Term(SemanticGroup):
- """
- Represents possible negation of expression.
-
- :param req: An object of :py:class:`Req`.
- :param boolean negate: Whether the result of children shall be negated.
- """
-
- def __init__(self, req, negate):
- assert isinstance(req, Req)
- self.req = req
- self.negate = negate
-
- def evaluate(self):
- res = self.req()
- return not res if self.negate else res
-
- def __str__(self):
- if self.negate:
- return '!' + str(self.req)
- return str(self.req)
-
-class Req(SemanticGroup):
- """
- Represents one of following subexpressions:
-
- * single requirement on particular profile
- * single requirement on particular class
- * a subexpression
- """
- pass
-
-class ReqCond(Req):
- """
- Represents single requirement on particular class or profile.
-
- :param str kind: Name identifying kind of thing this belongs. For example
- ``'class'`` or ``'profile'``.
- :param callable version_getter: Is a function called to get version of
- either profile or CIM class. It must return corresponding version string
- if the profile or class is registered and ``None`` otherwise.
- Version string is read from ``RegisteredVersion`` property of
- ``CIM_RegisteredProfile``. If a class is being queried, version
- shall be taken from ``Version`` qualifier of given class.
- :param str name: Name of profile or CIM class to check for. In case
- of a profile, it is compared to ``RegisteredName`` property of
- ``CIM_RegisteredProfile``. If any instance of this class has matching
- name, it's version will be checked. If no matching instance is found,
- instances of ``CIM_RegisteredSubProfile`` are queried the same way.
- Failing to find it results in ``False``.
- :param str cond: Is a version requirement. Check the grammer above for
- ``cond`` non-terminal.
- """
-
- def __init__(self, kind, version_getter, name, cond=None):
- assert isinstance(kind, basestring)
- assert callable(version_getter)
- assert isinstance(name, basestring)
- assert cond is None or (isinstance(cond, tuple) and len(cond) == 2)
- self.kind = kind
- self.version_getter = version_getter
- self.name = name
- self.cond = cond
-
- def evaluate(self):
- version = self.version_getter(self.name)
- return version and (not self.cond or self._check_version(version))
-
- def _check_version(self, version):
- """
- Checks whether the version of profile or class satisfies the
- requirement. Version strings are first split into a list of integers
- (that were delimited with a dot) and then they are compared in
- descending order from the most signigicant down.
-
- :param str version: Version of profile or class to check.
- """
- opsign, cmpver = self.cond
- return cmp_version(version, cmpver, opsign)
-
- def __str__(self):
- return '{%s "%s"%s}' % (
- self.kind, self.name, ' %s %s' % self.cond if self.cond else '')
-
-class Subexpr(Req):
- """
- Represents a subexpression originally enclosed in brackets.
- """
-
- def __init__(self, expr):
- assert isinstance(expr, Expr)
- self.expr = expr
-
- def evaluate(self):
- return self.expr()
-
- def __str__(self):
- return "(%s)" % self.expr
-
-class TreeBuilder(object):
- """
- A stack interface for parser. It defines methods modifying the stack with
- additional checks.
- """
-
- def __init__(self, stack, profile_version_getter, class_version_getter):
- if not isinstance(stack, list):
- raise TypeError("stack needs to be empty!")
- if stack:
- stack[:] = []
- self.stack = stack
- self.profile_version_getter = profile_version_getter
- self.class_version_getter = class_version_getter
-
- def expr(self, strg, loc, toks):
- """
- Operates upon a stack. It takes either one or two *terms* there
- and makes an expression object out of them. Terms need to be delimited
- with logical operator.
- """
- assert len(self.stack) > 0
- if not isinstance(self.stack[-1], (Term, Expr)):
- raise ParseException("Invalid expression (stopped at char %d)."
- % loc)
- if len(self.stack) >= 3 and self.stack[-2] in ('&', '|'):
- assert isinstance(self.stack[-3], Term)
- if self.stack[-2] == '&':
- expr = And(self.stack[-3], self.stack[-1])
- else:
- expr = Or(self.stack[-3], self.stack[-1])
- self.stack.pop()
- self.stack.pop()
- elif not isinstance(self.stack[-1], Expr):
- expr = Expr(self.stack[-1])
- else:
- expr = self.stack[-1]
- self.stack[-1] = expr
-
- def term(self, strg, loc, toks):
- """
- Creates a ``term`` out of requirement (``req`` non-terminal).
- """
- assert len(self.stack) > 0
- assert isinstance(self.stack[-1], Req)
- self.stack[-1] = Term(self.stack[-1], toks[0] == '!')
-
- def subexpr(self, strg, loc, toks):
- """
- Operates upon a stack. It creates an instance of :py:class:`Subexpr`
- out of :py:class:`Expr` which is enclosed in brackets.
- """
- assert len(self.stack) > 1
- assert self.stack[-2] == '('
- assert isinstance(self.stack[-1], Expr)
- assert len(toks) > 0 and toks[-1] == ')'
- self.stack[-2] = Subexpr(self.stack[-1])
- self.stack.pop()
-
- def push_class(self, strg, loc, toks):
- """
- Handles ``clsreq_cond`` non-terminal in one go. It extracts
- corresponding tokens and pushes an object of :py:class:`ReqCond` to a
- stack.
- """
- assert toks[0] == 'class'
- assert len(toks) >= 2
- name = toks[1]
- condition = None
- if len(toks) > 2 and toks[2] in OP_MAP:
- assert len(toks) >= 4
- condition = toks[2], toks[3]
- self.stack.append(ReqCond('class', self.class_version_getter,
- name, condition))
-
- def push_profile(self, strg, loc, toks):
- """
- Handles ``profile_cond`` non-terminal in one go. It behaves in the same
- way as :py:meth:`push_profile`.
- """
- index = 0
- if toks[0] == 'profile':
- index = 1
- assert len(toks) > index
- name = toks[index]
- index += 1
- condition = None
- if len(toks) > index and toks[index] in OP_MAP:
- assert len(toks) >= index + 2
- condition = toks[index], toks[index + 1]
- self.stack.append(ReqCond('profile', self.profile_version_getter,
- name, condition))
-
- def push_literal(self, strg, loc, toks):
- """
- Pushes operators to a stack.
- """
- assert toks[0] in ('&', '|', '(')
- if toks[0] == '(':
- assert not self.stack or self.stack[-1] in ('&', '|')
- else:
- assert len(self.stack) > 0
- assert isinstance(self.stack[-1], Term)
- self.stack.append(toks[0])
-
-def bnf_parser(stack, profile_version_getter, class_version_getter):
- """
- Builds a parser operating on provided stack.
-
- :param list stack: Stack to operate on. It will contain the resulting
- :py:class:`Expr` object when the parsing is successfully over -
- it will be the only item in the list. It needs to be initially empty.
- :param callable profile_version_getter: Function returning version
- of registered profile or ``None`` if not present.
- :param callable class_version_getter: Fucntion returning version
- of registered class or ``None`` if not present.
- :returns: Parser object.
- :rtype: :py:class:`pyparsing,ParserElement`
- """
- if not isinstance(stack, list):
- raise TypeError("stack must be a list!")
- builder = TreeBuilder(stack, profile_version_getter, class_version_getter)
-
- ntop = ((Literal('&') | Literal('|')) + FollowedBy(Regex('["a-zA-Z\(!]'))) \
- .setName('op').setParseAction(builder.push_literal)
- ntversion = Regex(r'[0-9]+(\.[0-9]+)*').setName('version')
- ntcmpop = Regex(r'(<|=|>|!)=|<|>(?=\s*\d)').setName('cmpop')
- ntcond = (ntcmpop + ntversion).setName('cond')
- ntclsname = Regex(r'[a-zA-Z]+_[a-zA-Z][a-zA-Z0-9_]*').setName('clsname')
- ntclsname_quot = Combine(
- Literal('"').suppress()
- + ntclsname
- + Literal('"').suppress()).setName('clsname_quot')
- ntprofile_quot = Combine(
- Literal('"').suppress()
- + Regex(r'\w+[ +.a-zA-Z0-9_-]*')
- + Literal('"').suppress()).setName('profile_quot')
- ntprofile = Regex(r'\w+[+.a-zA-Z0-9_-]*').setName('profile')
- ntclsreq_cond = (
- Keyword('class')
- + (ntclsname_quot | ntclsname)
- + Optional(ntcond)).setName('clsreq_cond').setParseAction(
- builder.push_class)
- ntprofile_cond = (
- Optional(Keyword('profile'))
- + (ntprofile_quot | ntprofile)
- + Optional(ntcond)).setName('profile_cond').setParseAction(
- builder.push_profile)
- ntexpr = Forward().setName('expr')
- bracedexpr = (
- Literal('(').setParseAction(builder.push_literal)
- + ntexpr
- + Literal(')')).setParseAction(builder.subexpr)
- ntreq = (bracedexpr | ntclsreq_cond | ntprofile_cond).setName('req')
- ntterm = (Optional(Literal("!")) + ntreq + FollowedBy(Regex('[\)&\|]') | LineEnd()))\
- .setParseAction(builder.term)
- ntexpr << ntterm + ZeroOrMore(ntop + ntexpr).setParseAction(builder.expr)
-
- return ntexpr
-
-if __name__ == '__main__':
- def get_class_version(class_name):
- try:
- version = { 'lmi_logicalfile' : '0.1.2'
- , 'lmi_softwareidentity' : '3.2.1'
- , 'pg_computersystem' : '1.1.1'
- }[class_name.lower()]
- except KeyError:
- version = None
- return version
-
- def get_profile_version(profile_name):
- try:
- version = { 'openlmi software' : '0.1.2'
- , 'openlmi-software' : '1.3.4'
- , 'openlmi hardware' : '1.1.1'
- , 'openlmi-hardware' : '0.2.3'
- }[profile_name.lower()]
- except KeyError:
- version = None
- return version
-
- def test(s, expected):
- stack = []
- parser = bnf_parser(stack, get_profile_version, get_class_version)
- results = parser.parseString(s, parseAll=True)
- if len(stack) == 1:
- evalresult = stack[0]()
- if expected == evalresult:
- print "%s\t=>\tOK" % s
- else:
- print "%s\t=>\tFAILED" % s
- else:
- print "%s\t=>\tFAILED" % s
- print " stack: [%s]" % ', '.join(str(i) for i in stack)
-
- test( 'class LMI_SoftwareIdentity == 0.2.0', False)
- test( '"OpenLMI-Software" == 0.1.2 & "OpenLMI-Hardware" < 0.1.3', False)
- test( 'OpenLMI-Software<1|OpenLMI-Hardware!=1.2.4', True)
- test( '"OpenLMI Software" & profile "OpenLMI Hardware"'
- ' | ! class LMI_LogicalFile', True)
- test( 'profile OpenLMI-Software > 0.1.2 & !(class "PG_ComputerSystem"'
- ' == 2.3.4 | "OpenLMI Hardware")', False)
- test( 'OpenLMI-Software > 1.3 & OpenLMI-Software >= 1.3.4'
- ' & OpenLMI-Software < 1.3.4.1 & OpenLMI-Software <= 1.3.4'
- ' & OpenLMI-Software == 1.3.4', True)
- test( 'OpenLMI-Software < 1.3.4 | OpenLMI-Software > 1.3.4'
- ' | OpenLMI-Software != 1.3.4', False)
- test( '(! OpenLMI-Software == 1.3.4 | OpenLMI-Software <= 1.3.4.1)'
- ' & !(openlmi-software > 1.3.4 | Openlmi-software != 1.3.4)', True)
- for badexpr in (
- 'OpenLMI-Software > & OpenLMI-Hardware',
- 'classs LMI_SoftwareIdentity',
- 'OpenLMI-Software > 1.2.3 | == 5.4.3',
- '',
- '"OpenLMI-Software',
- 'OpenLMI-Software < > OpenLMI-Hardware',
- 'OpenlmiSoftware & (openLMI-Hardware ',
- 'OpenLMISoftware & ) OpenLMI-Hardare (',
- 'OpenLMISoftware | OpenlmiSoftware > "1.2.3"'
- ):
- try:
- test(badexpr, None)
- except ParseException:
- print "%s\t=>\tOK" % badexpr