Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# Authors: # Jason Gerard DeRose <jderose@redhat.com> # # Copyright (C) 2008 Red Hat # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
Plugin framework.
The classes in this module make heavy use of Python container emulation. If you are unfamiliar with this Python feature, see http://docs.python.org/ref/sequence-types.html """
# FIXME: Updated constants.TYPE_ERROR to use this clearer format from wehjit:
""" If the object has self.env.mode defined and that mode is production return True, otherwise return False. """
""" A read-only container with set/sequence behaviour.
This container acts as a proxy to an actual set-like object (a set, frozenset, or dict) that is passed to the constructor. To the extent possible in Python, this underlying set-like object cannot be modified through the SetProxy... which just means you wont do it accidentally. """ """ :param s: The target set-like object (a set, frozenset, or dict) """
""" Return the number of items in this container. """
""" Iterate (in ascending order) through keys. """
""" Return True if this container contains ``key``.
:param key: The key to test for membership. """
""" A read-only container with mapping behaviour.
This container acts as a proxy to an actual mapping object (a dict) that is passed to the constructor. To the extent possible in Python, this underlying mapping object cannot be modified through the DictProxy... which just means you wont do it accidentally.
Also see `SetProxy`. """ """ :param d: The target mapping object (a dict) """
""" Return the value corresponding to ``key``.
:param key: The key of the value you wish to retrieve. """
""" Iterate (in ascending order by key) through values. """
""" A mapping container whose values can be accessed as attributes.
For example:
>>> magic = MagicDict({'the_key': 'the value'}) >>> magic['the_key'] 'the value' >>> magic.the_key 'the value'
This container acts as a proxy to an actual mapping object (a dict) that is passed to the constructor. To the extent possible in Python, this underlying mapping object cannot be modified through the MagicDict... which just means you wont do it accidentally.
Also see `DictProxy` and `SetProxy`. """
""" Return the value corresponding to ``name``.
:param name: The name of the attribute you wish to retrieve. """
""" Base class for all plugins. """
'%s.%s' % (b.__module__, b.__name__) for b in cls.__bases__ ) else: raise TypeError( TYPE_ERROR % ( self.fullname + '.label', text.LazyText, type(self.label), self.label ) )
""" Return `API` instance passed to `set_api()`.
If `set_api()` has not yet been called, None is returned. """
""" Finalize plugin initialization.
This method calls `_on_finalize()` and locks the plugin object.
Subclasses should not override this method. Custom finalization is done in `_on_finalize()`. """ # No recursive calls! return
""" Do custom finalization.
This method is called from `finalize()`. Subclasses can override this method in order to add custom finalization. """
""" Finalize plugin initialization if it has not yet been finalized. """
""" Create a stub object for plugin attribute that isn't set until the finalization of the plugin initialization.
When the stub object is accessed, it calls `ensure_finalized()` to make sure the plugin initialization is finalized. The stub object is expected to be replaced with the actual attribute value during the finalization (preferably in `_on_finalize()`), otherwise an `AttributeError` is raised.
This is used to implement on-demand finalization of plugin initialization. """
except RuntimeError: # If the actual attribute value is not set in _on_finalize(), # getattr() calls __get__() again, which leads to infinite # recursion. This can happen only if the plugin is written # badly, so advise the developer about that instead of giving # them a generic "maximum recursion depth exceeded" error. raise AttributeError( "attribute '%s' of plugin '%s' was not set in finalize()" % (self.name, obj.name) )
""" Set reference to `API` instance. """
""" Call ``executable`` with ``args`` using subprocess.call().
If the call exits with a non-zero exit status, `ipalib.errors.SubprocessError` is raised, from which you can retrieve the exit code by checking the SubprocessError.returncode attribute.
This method does *not* return what ``executable`` sent to stdout... for that, use `Plugin.callread()`. """
""" Return 'module_name.class_name()' representation.
This representation could be used to instantiate this Plugin instance given the appropriate environment. """ self.__class__.__module__, self.__class__.__name__ )
""" Collects plugin classes as they are registered.
The Registrar does not instantiate plugins... it only implements the override logic and stores the plugins in a namespace per allowed base class.
The plugins are instantiated when `API.finalize()` is called. """ """ :param allowed: Base classes from which plugins accepted by this Registrar must subclass. """ dict(self.__base_iter()) )
""" Iterates through allowed bases that ``klass`` is a subclass of.
Raises `errors.PluginSubclassError` if ``klass`` is not a subclass of any allowed base.
:param klass: The plugin class to find bases for. """ plugin=klass, bases=self.__allowed.keys() )
""" Register the plugin ``klass``.
:param klass: A subclass of `Plugin` to attempt to register. :param override: If true, override an already registered plugin. """
# Raise DuplicateError if this exact class was already registered:
# Find the base class or raise SubclassError: # Check override: # Must use override=True to override: base=base.__name__, name=klass.__name__, plugin=klass, ) else: # There was nothing already registered to override: base=base.__name__, name=klass.__name__, plugin=klass, )
# The plugin is okay, add to sub_d:
# The plugin is okay, add to __registered:
""" Dynamic API object through which `Plugin` instances are accessed. """
'%s.%s() already called' % (self.__class__.__name__, name) )
""" Initialize environment variables and logging. """ # If logging has already been configured somewhere else (like in the # installer), don't add handlers or change levels:
# Add stderr handler: level = 'debug' else: level = 'info' else:
stream=sys.stderr, level=level, format=LOGGING_FORMAT_STDERR)]) # Add file handler: return # But not if in unit-test mode return try: os.makedirs(log_dir) except OSError: log.error('Could not create log_dir %r', log_dir) return
level = 'debug' filename=self.env.log, level=level, format=LOGGING_FORMAT_FILE)]) except IOError, e: log.error('Cannot open log file %r: %s', self.env.log, e) return
""" Add global options to an optparse.OptionParser instance. """ parser = optparse.OptionParser() parser.disable_interspersed_args() help='Set environment variable KEY to VAL', ) help='Load configuration from FILE', ) help='Produce full debuging output', ) help='Delegate the TGT to the IPA server', ) help='Produce more verbose output. A second -v displays the XML-RPC request', ) parser.add_option('-a', '--prompt-all', action='store_true', help='Prompt for ALL values (even if optional)' ) parser.add_option('-n', '--no-prompt', action='store_false', dest='interactive', help='Prompt for NO values (even if required)' ) parser.add_option('-f', '--no-fallback', action='store_false', dest='fallback', help='Only use the server configured in /etc/ipa/default.conf' ) "ipa help topics") "ipa help commands")
assert type(options.env) is list for item in options.env: try: (key, value) = item.split('=', 1) except ValueError: # FIXME: this should raise an IPA exception with an # error code. # --Jason, 2008-10-31 pass overrides[str(key.strip())] = value.strip() 'fallback', 'delegate'): overrides[key] = value
""" Load plugins from all standard locations.
`API.bootstrap` will automatically be called if it hasn't been already. """ self.import_plugins('ipaserver/install/plugins')
# FIXME: This method has no unit test """ Import modules in ``plugins`` sub-package of ``package``. """ for part in parts: if part == 'plugins': plugins = subpackage.plugins subpackage = plugins.__name__ break subpackage = parent.__getattribute__(part) parent = subpackage else: except ImportError, e: self.log.error( 'cannot import plugins sub-package %s: %s', subpackage, e ) raise e raise errors.PluginsPackageError( name=subpackage, file=plugins.__file__ ) 'skipping plugin module %s: %s', fullname, e.reason ) except StandardError, e: if self.env.startup_traceback: import traceback self.log.error('could not load plugin module %r\n%s', pyfile, traceback.format_exc()) raise e
""" Finalize the registration, instantiate the plugins.
`API.bootstrap` will automatically be called if it hasn't been already. """
""" Represents a plugin instance. """
def next(cls):
plugin_iter(base, (magic[k] for k in magic)) ) name in self.__d or hasattr(self, name) )
tuple(PluginInfo(p) for p in plugins.itervalues()) ) |