2008-07-27 23:34:25 -05:00
|
|
|
# Authors:
|
|
|
|
# Jason Gerard DeRose <jderose@redhat.com>
|
|
|
|
#
|
|
|
|
# Copyright (C) 2008 Red Hat
|
|
|
|
# see file 'COPYING' for use and warranty information
|
|
|
|
#
|
2010-12-09 06:59:11 -06:00
|
|
|
# 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.
|
2008-07-27 23:34:25 -05:00
|
|
|
#
|
|
|
|
# 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
|
2010-12-09 06:59:11 -06:00
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2008-07-27 23:34:25 -05:00
|
|
|
|
|
|
|
"""
|
2008-08-14 15:32:35 -05:00
|
|
|
Plugin framework.
|
2008-08-14 13:59:12 -05:00
|
|
|
|
|
|
|
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
|
2008-07-27 23:34:25 -05:00
|
|
|
"""
|
|
|
|
|
2008-08-05 01:33:09 -05:00
|
|
|
import re
|
2008-10-31 19:17:08 -05:00
|
|
|
import sys
|
2008-07-27 23:34:25 -05:00
|
|
|
import inspect
|
2008-10-30 02:11:33 -05:00
|
|
|
import threading
|
2008-10-31 14:27:42 -05:00
|
|
|
import os
|
|
|
|
from os import path
|
2008-11-06 12:57:21 -06:00
|
|
|
import subprocess
|
2009-01-27 17:28:50 -06:00
|
|
|
import optparse
|
2009-04-23 07:51:59 -05:00
|
|
|
import errors
|
2012-11-06 10:05:41 -06:00
|
|
|
import textwrap
|
2015-06-24 10:14:54 -05:00
|
|
|
import collections
|
2015-06-15 06:02:07 -05:00
|
|
|
import importlib
|
2012-11-06 10:05:41 -06:00
|
|
|
|
2008-12-22 16:41:24 -06:00
|
|
|
from config import Env
|
2010-02-08 06:03:28 -06:00
|
|
|
import text
|
2011-02-23 15:47:49 -06:00
|
|
|
from text import _
|
2009-01-02 01:46:45 -06:00
|
|
|
from base import ReadOnly, NameSpace, lock, islocked, check_name
|
2011-11-15 13:39:31 -06:00
|
|
|
from constants import DEFAULT_CONFIG
|
|
|
|
from ipapython.ipa_log_manager import *
|
2014-06-06 07:39:59 -05:00
|
|
|
from ipapython.version import VERSION, API_VERSION
|
2008-08-14 13:50:21 -05:00
|
|
|
|
2010-02-08 06:03:28 -06:00
|
|
|
# FIXME: Updated constants.TYPE_ERROR to use this clearer format from wehjit:
|
|
|
|
TYPE_ERROR = '%s: need a %r; got a %r: %r'
|
|
|
|
|
2011-02-09 16:02:10 -06:00
|
|
|
def is_production_mode(obj):
|
|
|
|
"""
|
|
|
|
If the object has self.env.mode defined and that mode is
|
|
|
|
production return True, otherwise return False.
|
|
|
|
"""
|
|
|
|
if getattr(obj, 'env', None) is None:
|
|
|
|
return False
|
|
|
|
if getattr(obj.env, 'mode', None) is None:
|
|
|
|
return False
|
|
|
|
return obj.env.mode == 'production'
|
|
|
|
|
2008-08-14 13:50:21 -05:00
|
|
|
|
2015-06-15 05:53:22 -05:00
|
|
|
# FIXME: This function has no unit test
|
|
|
|
def find_modules_in_dir(src_dir):
|
|
|
|
"""
|
|
|
|
Iterate through module names found in ``src_dir``.
|
|
|
|
"""
|
|
|
|
if not (os.path.abspath(src_dir) == src_dir and os.path.isdir(src_dir)):
|
|
|
|
return
|
|
|
|
if os.path.islink(src_dir):
|
|
|
|
return
|
|
|
|
suffix = '.py'
|
|
|
|
for name in sorted(os.listdir(src_dir)):
|
|
|
|
if not name.endswith(suffix):
|
|
|
|
continue
|
|
|
|
pyfile = os.path.join(src_dir, name)
|
|
|
|
if not os.path.isfile(pyfile):
|
|
|
|
continue
|
|
|
|
module = name[:-len(suffix)]
|
|
|
|
if module == '__init__':
|
|
|
|
continue
|
2015-06-15 06:02:07 -05:00
|
|
|
yield module
|
2015-06-15 05:53:22 -05:00
|
|
|
|
|
|
|
|
2013-08-01 07:55:10 -05:00
|
|
|
class Registry(object):
|
|
|
|
"""A decorator that makes plugins available to the API
|
|
|
|
|
|
|
|
Usage::
|
|
|
|
|
|
|
|
register = Registry()
|
|
|
|
|
|
|
|
@register()
|
|
|
|
class obj_mod(...):
|
|
|
|
...
|
|
|
|
|
|
|
|
For forward compatibility, make sure that the module-level instance of
|
|
|
|
this object is named "register".
|
|
|
|
"""
|
2015-06-24 10:14:54 -05:00
|
|
|
def __call__(self):
|
|
|
|
def decorator(cls):
|
|
|
|
API.register(cls)
|
|
|
|
return cls
|
2013-08-01 07:55:10 -05:00
|
|
|
|
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
2008-08-14 20:04:19 -05:00
|
|
|
class SetProxy(ReadOnly):
|
2008-08-14 20:24:51 -05:00
|
|
|
"""
|
2008-08-14 22:24:37 -05:00
|
|
|
A read-only container with set/sequence behaviour.
|
2008-08-14 20:24:51 -05:00
|
|
|
|
2008-08-14 22:24:37 -05:00
|
|
|
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.
|
2008-08-14 20:24:51 -05:00
|
|
|
"""
|
2008-08-14 20:04:19 -05:00
|
|
|
def __init__(self, s):
|
2008-08-14 22:24:37 -05:00
|
|
|
"""
|
|
|
|
:param s: The target set-like object (a set, frozenset, or dict)
|
|
|
|
"""
|
2008-08-14 20:04:19 -05:00
|
|
|
allowed = (set, frozenset, dict)
|
|
|
|
if type(s) not in allowed:
|
|
|
|
raise TypeError('%r not in %r' % (type(s), allowed))
|
|
|
|
self.__s = s
|
2011-06-16 10:31:41 -05:00
|
|
|
if not is_production_mode(self):
|
|
|
|
lock(self)
|
2008-08-14 20:04:19 -05:00
|
|
|
|
|
|
|
def __len__(self):
|
2008-08-14 22:24:37 -05:00
|
|
|
"""
|
2008-10-18 02:02:31 -05:00
|
|
|
Return the number of items in this container.
|
2008-08-14 22:24:37 -05:00
|
|
|
"""
|
2008-08-14 20:04:19 -05:00
|
|
|
return len(self.__s)
|
|
|
|
|
|
|
|
def __iter__(self):
|
2008-08-14 22:24:37 -05:00
|
|
|
"""
|
2008-10-18 02:02:31 -05:00
|
|
|
Iterate (in ascending order) through keys.
|
2008-08-14 22:24:37 -05:00
|
|
|
"""
|
2008-08-14 20:04:19 -05:00
|
|
|
for key in sorted(self.__s):
|
|
|
|
yield key
|
|
|
|
|
|
|
|
def __contains__(self, key):
|
2008-08-14 22:24:37 -05:00
|
|
|
"""
|
2008-10-18 02:02:31 -05:00
|
|
|
Return True if this container contains ``key``.
|
2008-08-14 22:24:37 -05:00
|
|
|
|
2008-10-18 02:02:31 -05:00
|
|
|
:param key: The key to test for membership.
|
2008-08-14 22:24:37 -05:00
|
|
|
"""
|
2008-08-14 20:04:19 -05:00
|
|
|
return key in self.__s
|
|
|
|
|
|
|
|
|
|
|
|
class DictProxy(SetProxy):
|
2008-08-14 20:24:51 -05:00
|
|
|
"""
|
2008-08-14 22:24:37 -05:00
|
|
|
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`.
|
2008-08-14 20:24:51 -05:00
|
|
|
"""
|
2008-08-14 20:04:19 -05:00
|
|
|
def __init__(self, d):
|
2008-08-14 22:24:37 -05:00
|
|
|
"""
|
|
|
|
:param d: The target mapping object (a dict)
|
|
|
|
"""
|
2008-08-14 20:04:19 -05:00
|
|
|
if type(d) is not dict:
|
|
|
|
raise TypeError('%r is not %r' % (type(d), dict))
|
|
|
|
self.__d = d
|
|
|
|
super(DictProxy, self).__init__(d)
|
|
|
|
|
|
|
|
def __getitem__(self, key):
|
|
|
|
"""
|
2008-10-18 02:02:31 -05:00
|
|
|
Return the value corresponding to ``key``.
|
2008-08-14 22:24:37 -05:00
|
|
|
|
|
|
|
:param key: The key of the value you wish to retrieve.
|
2008-08-14 20:04:19 -05:00
|
|
|
"""
|
|
|
|
return self.__d[key]
|
|
|
|
|
2008-08-15 00:19:02 -05:00
|
|
|
def __call__(self):
|
|
|
|
"""
|
2008-10-18 02:02:31 -05:00
|
|
|
Iterate (in ascending order by key) through values.
|
2008-08-15 00:19:02 -05:00
|
|
|
"""
|
|
|
|
for key in self:
|
|
|
|
yield self.__d[key]
|
|
|
|
|
2008-08-14 20:04:19 -05:00
|
|
|
|
2008-08-14 20:24:51 -05:00
|
|
|
class MagicDict(DictProxy):
|
|
|
|
"""
|
2008-10-17 21:50:34 -05:00
|
|
|
A mapping container whose values can be accessed as attributes.
|
2008-08-14 22:24:37 -05:00
|
|
|
|
2008-10-17 21:50:34 -05:00
|
|
|
For example:
|
2008-08-14 20:24:51 -05:00
|
|
|
|
2008-10-17 21:50:34 -05:00
|
|
|
>>> magic = MagicDict({'the_key': 'the value'})
|
|
|
|
>>> magic['the_key']
|
|
|
|
'the value'
|
|
|
|
>>> magic.the_key
|
|
|
|
'the value'
|
2008-08-14 20:24:51 -05:00
|
|
|
|
2008-08-14 22:24:37 -05:00
|
|
|
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`.
|
2008-08-14 20:24:51 -05:00
|
|
|
"""
|
|
|
|
|
|
|
|
def __getattr__(self, name):
|
|
|
|
"""
|
2008-10-18 02:02:31 -05:00
|
|
|
Return the value corresponding to ``name``.
|
2008-08-14 22:24:37 -05:00
|
|
|
|
|
|
|
:param name: The name of the attribute you wish to retrieve.
|
2008-08-14 20:24:51 -05:00
|
|
|
"""
|
|
|
|
try:
|
|
|
|
return self[name]
|
|
|
|
except KeyError:
|
2008-08-14 22:24:37 -05:00
|
|
|
raise AttributeError('no magic attribute %r' % name)
|
2008-08-14 20:24:51 -05:00
|
|
|
|
|
|
|
|
2008-08-14 02:10:07 -05:00
|
|
|
class Plugin(ReadOnly):
|
|
|
|
"""
|
|
|
|
Base class for all plugins.
|
|
|
|
"""
|
|
|
|
|
2011-11-03 05:42:17 -05:00
|
|
|
finalize_early = True
|
|
|
|
|
2010-02-08 06:03:28 -06:00
|
|
|
label = None
|
|
|
|
|
2008-11-24 11:09:30 -06:00
|
|
|
def __init__(self):
|
2010-01-27 06:59:09 -06:00
|
|
|
self.__api = None
|
2011-11-03 05:42:17 -05:00
|
|
|
self.__finalize_called = False
|
|
|
|
self.__finalized = False
|
|
|
|
self.__finalize_lock = threading.RLock()
|
2008-12-17 22:47:43 -06:00
|
|
|
cls = self.__class__
|
|
|
|
self.name = cls.__name__
|
|
|
|
self.module = cls.__module__
|
|
|
|
self.fullname = '%s.%s' % (self.module, self.name)
|
2009-10-14 16:08:30 -05:00
|
|
|
self.bases = tuple(
|
|
|
|
'%s.%s' % (b.__module__, b.__name__) for b in cls.__bases__
|
|
|
|
)
|
2011-02-21 13:54:05 -06:00
|
|
|
self.doc = _(cls.__doc__)
|
2011-02-23 15:40:17 -06:00
|
|
|
if not self.doc.msg:
|
2008-12-18 00:08:52 -06:00
|
|
|
self.summary = '<%s>' % self.fullname
|
|
|
|
else:
|
2011-02-23 15:40:17 -06:00
|
|
|
self.summary = unicode(self.doc).split('\n\n', 1)[0].strip()
|
2011-11-15 13:39:31 -06:00
|
|
|
log_mgr.get_logger(self, True)
|
2010-02-08 06:03:28 -06:00
|
|
|
if self.label is None:
|
|
|
|
self.label = text.FixMe(self.name + '.label')
|
|
|
|
if not isinstance(self.label, text.LazyText):
|
|
|
|
raise TypeError(
|
|
|
|
TYPE_ERROR % (
|
|
|
|
self.fullname + '.label',
|
|
|
|
text.LazyText,
|
|
|
|
type(self.label),
|
|
|
|
self.label
|
|
|
|
)
|
|
|
|
)
|
2008-11-24 11:09:30 -06:00
|
|
|
|
2008-08-14 02:10:07 -05:00
|
|
|
def __get_api(self):
|
|
|
|
"""
|
2011-11-03 05:42:17 -05:00
|
|
|
Return `API` instance passed to `set_api()`.
|
2008-10-18 02:02:31 -05:00
|
|
|
|
2011-11-03 05:42:17 -05:00
|
|
|
If `set_api()` has not yet been called, None is returned.
|
2008-08-14 02:10:07 -05:00
|
|
|
"""
|
|
|
|
return self.__api
|
|
|
|
api = property(__get_api)
|
|
|
|
|
2008-09-21 16:50:56 -05:00
|
|
|
def finalize(self):
|
2008-08-14 02:10:07 -05:00
|
|
|
"""
|
2011-11-03 05:42:17 -05:00
|
|
|
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()`.
|
2008-08-14 02:10:07 -05:00
|
|
|
"""
|
2011-11-03 05:42:17 -05:00
|
|
|
with self.__finalize_lock:
|
|
|
|
assert self.__finalized is False
|
|
|
|
if self.__finalize_called:
|
|
|
|
# No recursive calls!
|
|
|
|
return
|
|
|
|
self.__finalize_called = True
|
|
|
|
self._on_finalize()
|
|
|
|
self.__finalized = True
|
|
|
|
if not is_production_mode(self):
|
|
|
|
lock(self)
|
|
|
|
|
|
|
|
def _on_finalize(self):
|
|
|
|
"""
|
|
|
|
Do custom finalization.
|
|
|
|
|
|
|
|
This method is called from `finalize()`. Subclasses can override this
|
|
|
|
method in order to add custom finalization.
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
|
|
|
def ensure_finalized(self):
|
|
|
|
"""
|
|
|
|
Finalize plugin initialization if it has not yet been finalized.
|
|
|
|
"""
|
|
|
|
with self.__finalize_lock:
|
|
|
|
if not self.__finalized:
|
|
|
|
self.finalize()
|
|
|
|
|
|
|
|
class finalize_attr(object):
|
|
|
|
"""
|
|
|
|
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.
|
|
|
|
"""
|
|
|
|
__slots__ = ('name', 'value')
|
|
|
|
|
|
|
|
def __init__(self, name, value=None):
|
|
|
|
self.name = name
|
|
|
|
self.value = value
|
|
|
|
|
|
|
|
def __get__(self, obj, cls):
|
|
|
|
if obj is None or obj.api is None:
|
|
|
|
return self.value
|
|
|
|
obj.ensure_finalized()
|
|
|
|
try:
|
|
|
|
return getattr(obj, self.name)
|
|
|
|
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)
|
|
|
|
)
|
2008-08-14 02:10:07 -05:00
|
|
|
|
2008-09-21 16:30:19 -05:00
|
|
|
def set_api(self, api):
|
|
|
|
"""
|
|
|
|
Set reference to `API` instance.
|
|
|
|
"""
|
|
|
|
assert self.__api is None, 'set_api() can only be called once'
|
|
|
|
assert api is not None, 'set_api() argument cannot be None'
|
|
|
|
self.__api = api
|
2008-10-30 15:11:24 -05:00
|
|
|
if not isinstance(api, API):
|
|
|
|
return
|
|
|
|
for name in api:
|
|
|
|
assert not hasattr(self, name)
|
|
|
|
setattr(self, name, api[name])
|
2011-11-15 13:39:31 -06:00
|
|
|
for name in ('env', 'context'):
|
2008-10-30 15:11:24 -05:00
|
|
|
if hasattr(api, name):
|
|
|
|
assert not hasattr(self, name)
|
|
|
|
setattr(self, name, getattr(api, name))
|
2008-09-21 16:30:19 -05:00
|
|
|
|
2008-12-21 18:12:00 -06:00
|
|
|
def call(self, executable, *args):
|
2008-11-06 12:57:21 -06:00
|
|
|
"""
|
2008-12-21 18:12:00 -06:00
|
|
|
Call ``executable`` with ``args`` using subprocess.call().
|
2008-11-06 12:57:21 -06:00
|
|
|
|
2008-12-21 18:12:00 -06:00
|
|
|
If the call exits with a non-zero exit status,
|
2009-04-23 07:51:59 -05:00
|
|
|
`ipalib.errors.SubprocessError` is raised, from which you can retrieve
|
2008-12-21 18:12:00 -06:00
|
|
|
the exit code by checking the SubprocessError.returncode attribute.
|
|
|
|
|
|
|
|
This method does *not* return what ``executable`` sent to stdout... for
|
|
|
|
that, use `Plugin.callread()`.
|
2008-11-06 12:57:21 -06:00
|
|
|
"""
|
2008-12-21 18:12:00 -06:00
|
|
|
argv = (executable,) + args
|
|
|
|
self.debug('Calling %r', argv)
|
2009-01-03 19:02:58 -06:00
|
|
|
code = subprocess.call(argv)
|
|
|
|
if code != 0:
|
2009-04-23 07:51:59 -05:00
|
|
|
raise errors.SubprocessError(returncode=code, argv=argv)
|
2008-11-06 12:57:21 -06:00
|
|
|
|
2008-08-14 02:10:07 -05:00
|
|
|
def __repr__(self):
|
|
|
|
"""
|
2008-10-18 02:02:31 -05:00
|
|
|
Return 'module_name.class_name()' representation.
|
|
|
|
|
|
|
|
This representation could be used to instantiate this Plugin
|
|
|
|
instance given the appropriate environment.
|
2008-08-14 02:10:07 -05:00
|
|
|
"""
|
2008-09-08 16:51:05 -05:00
|
|
|
return '%s.%s()' % (
|
2008-08-14 02:10:07 -05:00
|
|
|
self.__class__.__module__,
|
|
|
|
self.__class__.__name__
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2015-06-24 10:14:54 -05:00
|
|
|
class Registrar(collections.Mapping):
|
|
|
|
"""
|
|
|
|
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.
|
|
|
|
"""
|
|
|
|
def __init__(self):
|
|
|
|
self.__registry = collections.OrderedDict()
|
|
|
|
|
|
|
|
def __call__(self, klass, override=False):
|
|
|
|
"""
|
|
|
|
Register the plugin ``klass``.
|
|
|
|
|
|
|
|
:param klass: A subclass of `Plugin` to attempt to register.
|
|
|
|
:param override: If true, override an already registered plugin.
|
|
|
|
"""
|
|
|
|
if not inspect.isclass(klass):
|
|
|
|
raise TypeError('plugin must be a class; got %r' % klass)
|
|
|
|
|
|
|
|
# Raise DuplicateError if this exact class was already registered:
|
|
|
|
if klass in self.__registry:
|
|
|
|
raise errors.PluginDuplicateError(plugin=klass)
|
|
|
|
|
|
|
|
# The plugin is okay, add to __registry:
|
|
|
|
self.__registry[klass] = dict(override=override)
|
|
|
|
|
|
|
|
def __getitem__(self, key):
|
|
|
|
return self.__registry[key]
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
return iter(self.__registry)
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
return len(self.__registry)
|
|
|
|
|
|
|
|
|
2008-08-14 20:32:20 -05:00
|
|
|
class API(DictProxy):
|
2008-08-14 16:40:37 -05:00
|
|
|
"""
|
|
|
|
Dynamic API object through which `Plugin` instances are accessed.
|
|
|
|
"""
|
2008-08-12 18:40:36 -05:00
|
|
|
|
2015-06-24 10:14:54 -05:00
|
|
|
register = Registrar()
|
|
|
|
|
2015-06-15 06:02:07 -05:00
|
|
|
def __init__(self, allowed, modules):
|
2015-06-24 10:14:54 -05:00
|
|
|
self.__plugins = {base: {} for base in allowed}
|
2015-06-15 06:02:07 -05:00
|
|
|
self.modules = modules
|
2008-08-14 17:13:42 -05:00
|
|
|
self.__d = dict()
|
2008-10-27 00:28:06 -05:00
|
|
|
self.__done = set()
|
2008-10-27 16:19:49 -05:00
|
|
|
self.env = Env()
|
2008-08-14 20:32:20 -05:00
|
|
|
super(API, self).__init__(self.__d)
|
2008-08-08 12:11:29 -05:00
|
|
|
|
2008-10-27 00:28:06 -05:00
|
|
|
def __doing(self, name):
|
|
|
|
if name in self.__done:
|
|
|
|
raise StandardError(
|
|
|
|
'%s.%s() already called' % (self.__class__.__name__, name)
|
|
|
|
)
|
|
|
|
self.__done.add(name)
|
|
|
|
|
|
|
|
def __do_if_not_done(self, name):
|
|
|
|
if name not in self.__done:
|
|
|
|
getattr(self, name)()
|
|
|
|
|
|
|
|
def isdone(self, name):
|
|
|
|
return name in self.__done
|
|
|
|
|
2012-12-13 08:23:32 -06:00
|
|
|
def bootstrap(self, parser=None, **overrides):
|
2008-10-27 00:28:06 -05:00
|
|
|
"""
|
2008-10-31 14:27:42 -05:00
|
|
|
Initialize environment variables and logging.
|
2008-10-27 00:28:06 -05:00
|
|
|
"""
|
|
|
|
self.__doing('bootstrap')
|
2008-10-27 16:19:49 -05:00
|
|
|
self.env._bootstrap(**overrides)
|
2008-10-31 14:27:42 -05:00
|
|
|
self.env._finalize_core(**dict(DEFAULT_CONFIG))
|
2011-11-15 13:39:31 -06:00
|
|
|
object.__setattr__(self, 'log_mgr', log_mgr)
|
|
|
|
log = log_mgr.root_logger
|
2008-10-31 14:27:42 -05:00
|
|
|
object.__setattr__(self, 'log', log)
|
2014-03-05 06:59:10 -06:00
|
|
|
|
|
|
|
# Add the argument parser
|
|
|
|
if not parser:
|
|
|
|
parser = self.build_global_parser()
|
|
|
|
object.__setattr__(self, 'parser', parser)
|
|
|
|
|
2010-02-09 05:57:23 -06:00
|
|
|
# If logging has already been configured somewhere else (like in the
|
|
|
|
# installer), don't add handlers or change levels:
|
2011-11-15 13:39:31 -06:00
|
|
|
if log_mgr.configure_state != 'default' or self.env.validate_api:
|
2010-02-09 05:57:23 -06:00
|
|
|
return
|
|
|
|
|
2011-11-30 10:08:42 -06:00
|
|
|
log_mgr.default_level = 'info'
|
2011-11-15 13:39:31 -06:00
|
|
|
log_mgr.configure_from_env(self.env, configure_state='api')
|
2008-10-31 14:27:42 -05:00
|
|
|
# Add stderr handler:
|
2011-11-15 13:39:31 -06:00
|
|
|
level = 'info'
|
2008-10-31 14:27:42 -05:00
|
|
|
if self.env.debug:
|
2011-11-15 13:39:31 -06:00
|
|
|
level = 'debug'
|
2008-10-31 14:27:42 -05:00
|
|
|
else:
|
2011-02-11 16:24:20 -06:00
|
|
|
if self.env.context == 'cli':
|
|
|
|
if self.env.verbose > 0:
|
2011-11-15 13:39:31 -06:00
|
|
|
level = 'info'
|
2011-02-11 16:24:20 -06:00
|
|
|
else:
|
2011-11-15 13:39:31 -06:00
|
|
|
level = 'warning'
|
|
|
|
|
|
|
|
if log_mgr.handlers.has_key('console'):
|
|
|
|
log_mgr.remove_handler('console')
|
|
|
|
log_mgr.create_log_handlers([dict(name='console',
|
|
|
|
stream=sys.stderr,
|
|
|
|
level=level,
|
|
|
|
format=LOGGING_FORMAT_STDERR)])
|
2013-06-06 05:52:08 -05:00
|
|
|
|
2008-10-31 14:27:42 -05:00
|
|
|
# Add file handler:
|
2008-11-07 03:26:38 -06:00
|
|
|
if self.env.mode in ('dummy', 'unit_test'):
|
2009-01-22 10:58:35 -06:00
|
|
|
return # But not if in unit-test mode
|
2009-01-30 21:53:32 -06:00
|
|
|
if self.env.log is None:
|
|
|
|
return
|
2008-10-31 14:27:42 -05:00
|
|
|
log_dir = path.dirname(self.env.log)
|
|
|
|
if not path.isdir(log_dir):
|
|
|
|
try:
|
|
|
|
os.makedirs(log_dir)
|
|
|
|
except OSError:
|
2009-02-05 20:08:33 -06:00
|
|
|
log.error('Could not create log_dir %r', log_dir)
|
2008-10-31 14:27:42 -05:00
|
|
|
return
|
2011-11-15 13:39:31 -06:00
|
|
|
|
|
|
|
level = 'info'
|
|
|
|
if self.env.debug:
|
|
|
|
level = 'debug'
|
2009-02-05 20:08:33 -06:00
|
|
|
try:
|
2011-11-15 13:39:31 -06:00
|
|
|
log_mgr.create_log_handlers([dict(name='file',
|
|
|
|
filename=self.env.log,
|
|
|
|
level=level,
|
|
|
|
format=LOGGING_FORMAT_FILE)])
|
2009-02-05 20:08:33 -06:00
|
|
|
except IOError, e:
|
2011-11-15 13:39:31 -06:00
|
|
|
log.error('Cannot open log file %r: %s', self.env.log, e)
|
2009-02-05 20:08:33 -06:00
|
|
|
return
|
2008-10-31 14:27:42 -05:00
|
|
|
|
2009-01-28 14:05:26 -06:00
|
|
|
def build_global_parser(self, parser=None, context=None):
|
2009-01-27 17:28:50 -06:00
|
|
|
"""
|
|
|
|
Add global options to an optparse.OptionParser instance.
|
|
|
|
"""
|
|
|
|
if parser is None:
|
2012-11-06 10:05:41 -06:00
|
|
|
parser = optparse.OptionParser(
|
|
|
|
add_help_option=False,
|
|
|
|
formatter=IPAHelpFormatter(),
|
|
|
|
usage='%prog [global-options] COMMAND [command-options]',
|
|
|
|
description='Manage an IPA domain',
|
2014-06-06 07:39:59 -05:00
|
|
|
version=('VERSION: %s, API_VERSION: %s'
|
|
|
|
% (VERSION, API_VERSION)),
|
2012-11-06 10:05:41 -06:00
|
|
|
epilog='\n'.join([
|
|
|
|
'See "ipa help topics" for available help topics.',
|
|
|
|
'See "ipa help <TOPIC>" for more information on a '
|
|
|
|
'specific topic.',
|
|
|
|
'See "ipa help commands" for the full list of commands.',
|
2012-11-08 07:57:39 -06:00
|
|
|
'See "ipa <COMMAND> --help" for more information on a '
|
2012-11-06 10:05:41 -06:00
|
|
|
'specific command.',
|
|
|
|
]))
|
2009-01-27 17:28:50 -06:00
|
|
|
parser.disable_interspersed_args()
|
2012-11-06 10:05:41 -06:00
|
|
|
parser.add_option("-h", "--help", action="help",
|
|
|
|
help='Show this help message and exit')
|
|
|
|
|
2009-01-27 17:28:50 -06:00
|
|
|
parser.add_option('-e', dest='env', metavar='KEY=VAL', action='append',
|
|
|
|
help='Set environment variable KEY to VAL',
|
|
|
|
)
|
|
|
|
parser.add_option('-c', dest='conf', metavar='FILE',
|
|
|
|
help='Load configuration from FILE',
|
|
|
|
)
|
|
|
|
parser.add_option('-d', '--debug', action='store_true',
|
|
|
|
help='Produce full debuging output',
|
|
|
|
)
|
2012-02-15 10:06:54 -06:00
|
|
|
parser.add_option('--delegate', action='store_true',
|
|
|
|
help='Delegate the TGT to the IPA server',
|
|
|
|
)
|
2010-06-03 16:07:24 -05:00
|
|
|
parser.add_option('-v', '--verbose', action='count',
|
|
|
|
help='Produce more verbose output. A second -v displays the XML-RPC request',
|
2009-01-27 17:28:50 -06:00
|
|
|
)
|
|
|
|
if context == 'cli':
|
|
|
|
parser.add_option('-a', '--prompt-all', action='store_true',
|
2009-01-28 14:05:26 -06:00
|
|
|
help='Prompt for ALL values (even if optional)'
|
2008-10-31 19:17:08 -05:00
|
|
|
)
|
2009-01-27 17:28:50 -06:00
|
|
|
parser.add_option('-n', '--no-prompt', action='store_false',
|
2009-01-28 14:05:26 -06:00
|
|
|
dest='interactive',
|
|
|
|
help='Prompt for NO values (even if required)'
|
2009-01-27 17:28:50 -06:00
|
|
|
)
|
2010-07-26 16:54:38 -05:00
|
|
|
parser.add_option('-f', '--no-fallback', action='store_false',
|
|
|
|
dest='fallback',
|
|
|
|
help='Only use the server configured in /etc/ipa/default.conf'
|
|
|
|
)
|
2009-11-18 15:50:16 -06:00
|
|
|
|
2009-01-27 17:28:50 -06:00
|
|
|
return parser
|
|
|
|
|
|
|
|
def bootstrap_with_global_options(self, parser=None, context=None):
|
2009-01-28 14:05:26 -06:00
|
|
|
parser = self.build_global_parser(parser, context)
|
2009-01-27 17:28:50 -06:00
|
|
|
(options, args) = parser.parse_args()
|
2008-10-31 19:17:08 -05:00
|
|
|
overrides = {}
|
|
|
|
if options.env is not None:
|
|
|
|
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()
|
2010-07-26 16:54:38 -05:00
|
|
|
for key in ('conf', 'debug', 'verbose', 'prompt_all', 'interactive',
|
2012-02-15 10:06:54 -06:00
|
|
|
'fallback', 'delegate'):
|
2008-10-31 19:17:08 -05:00
|
|
|
value = getattr(options, key, None)
|
|
|
|
if value is not None:
|
|
|
|
overrides[key] = value
|
2010-01-26 07:39:00 -06:00
|
|
|
if hasattr(options, 'prod'):
|
|
|
|
overrides['webui_prod'] = options.prod
|
2008-10-31 19:17:08 -05:00
|
|
|
if context is not None:
|
|
|
|
overrides['context'] = context
|
2012-12-13 08:23:32 -06:00
|
|
|
self.bootstrap(parser, **overrides)
|
2009-10-13 12:28:00 -05:00
|
|
|
return (options, args)
|
2008-10-27 00:28:06 -05:00
|
|
|
|
2008-10-28 00:39:43 -05:00
|
|
|
def load_plugins(self):
|
2008-10-27 00:53:44 -05:00
|
|
|
"""
|
|
|
|
Load plugins from all standard locations.
|
|
|
|
|
|
|
|
`API.bootstrap` will automatically be called if it hasn't been
|
|
|
|
already.
|
|
|
|
"""
|
|
|
|
self.__doing('load_plugins')
|
|
|
|
self.__do_if_not_done('bootstrap')
|
2008-11-07 03:26:38 -06:00
|
|
|
if self.env.mode in ('dummy', 'unit_test'):
|
2008-10-27 01:23:43 -05:00
|
|
|
return
|
2015-06-15 06:02:07 -05:00
|
|
|
for module in self.modules:
|
|
|
|
self.import_plugins(module)
|
2009-02-12 03:10:12 -06:00
|
|
|
|
|
|
|
# FIXME: This method has no unit test
|
2015-06-15 06:02:07 -05:00
|
|
|
def import_plugins(self, module):
|
2009-02-12 03:10:12 -06:00
|
|
|
"""
|
2015-06-15 06:02:07 -05:00
|
|
|
Import plugins from ``module``.
|
|
|
|
|
|
|
|
:param module: Name of the module to import. This might be a wildcard
|
|
|
|
in the form ```package.*``` in which case all modules
|
|
|
|
from the given package are loaded.
|
2009-02-12 03:10:12 -06:00
|
|
|
"""
|
2015-06-15 06:02:07 -05:00
|
|
|
if module.endswith('.*'):
|
|
|
|
subpackage = module[:-2]
|
2009-02-12 03:10:12 -06:00
|
|
|
try:
|
2015-06-15 06:02:07 -05:00
|
|
|
plugins = importlib.import_module(subpackage)
|
|
|
|
except ImportError, e:
|
|
|
|
self.log.error("cannot import plugins sub-package %s: %s",
|
|
|
|
subpackage, e)
|
|
|
|
raise
|
|
|
|
package, dot, part = subpackage.rpartition('.')
|
|
|
|
parent = sys.modules[package]
|
|
|
|
|
|
|
|
parent_dir = path.dirname(path.abspath(parent.__file__))
|
|
|
|
plugins_dir = path.dirname(path.abspath(plugins.__file__))
|
|
|
|
if parent_dir == plugins_dir:
|
|
|
|
raise errors.PluginsPackageError(
|
|
|
|
name=subpackage, file=plugins.__file__
|
2009-02-12 03:10:12 -06:00
|
|
|
)
|
2015-06-15 06:02:07 -05:00
|
|
|
|
|
|
|
self.log.debug("importing all plugin modules in %s...", subpackage)
|
|
|
|
modules = find_modules_in_dir(plugins_dir)
|
|
|
|
modules = ['.'.join((subpackage, name)) for name in modules]
|
|
|
|
else:
|
|
|
|
modules = [module]
|
|
|
|
|
|
|
|
for name in modules:
|
|
|
|
self.log.debug("importing plugin module %s", name)
|
|
|
|
try:
|
2015-06-22 05:16:34 -05:00
|
|
|
module = importlib.import_module(name)
|
2015-06-15 06:02:07 -05:00
|
|
|
except errors.SkipPluginModule, e:
|
|
|
|
self.log.debug("skipping plugin module %s: %s", name, e.reason)
|
2009-02-12 03:10:12 -06:00
|
|
|
except StandardError, e:
|
2010-06-25 12:37:27 -05:00
|
|
|
if self.env.startup_traceback:
|
|
|
|
import traceback
|
2015-06-15 06:02:07 -05:00
|
|
|
self.log.error("could not load plugin module %s\n%s", name,
|
|
|
|
traceback.format_exc())
|
2012-05-21 04:03:21 -05:00
|
|
|
raise
|
2015-06-22 05:16:34 -05:00
|
|
|
else:
|
|
|
|
self.add_module(module)
|
|
|
|
|
|
|
|
def add_module(self, module):
|
|
|
|
"""
|
|
|
|
Add plugins from the ``module``.
|
|
|
|
|
|
|
|
:param module: A module from which to add plugins.
|
|
|
|
"""
|
|
|
|
for name in dir(module):
|
|
|
|
klass = getattr(module, name)
|
|
|
|
if not inspect.isclass(klass):
|
|
|
|
continue
|
|
|
|
if klass not in self.register:
|
|
|
|
continue
|
|
|
|
kwargs = self.register[klass]
|
|
|
|
self.add_plugin(klass, **kwargs)
|
2008-10-27 00:53:44 -05:00
|
|
|
|
2015-06-24 10:14:54 -05:00
|
|
|
def add_plugin(self, klass, override=False):
|
|
|
|
"""
|
|
|
|
Add the plugin ``klass``.
|
|
|
|
|
|
|
|
:param klass: A subclass of `Plugin` to attempt to add.
|
|
|
|
:param override: If true, override an already added plugin.
|
|
|
|
"""
|
|
|
|
if not inspect.isclass(klass):
|
|
|
|
raise TypeError('plugin must be a class; got %r' % klass)
|
|
|
|
|
|
|
|
# Find the base class or raise SubclassError:
|
|
|
|
found = False
|
|
|
|
for (base, sub_d) in self.__plugins.iteritems():
|
|
|
|
if not issubclass(klass, base):
|
|
|
|
continue
|
|
|
|
|
|
|
|
found = True
|
|
|
|
|
2015-06-22 05:16:34 -05:00
|
|
|
if sub_d.get(klass.__name__) is klass:
|
|
|
|
continue
|
|
|
|
|
2015-06-24 10:14:54 -05:00
|
|
|
# Check override:
|
|
|
|
if klass.__name__ in sub_d:
|
|
|
|
if not override:
|
|
|
|
# Must use override=True to override:
|
|
|
|
raise errors.PluginOverrideError(
|
|
|
|
base=base.__name__,
|
|
|
|
name=klass.__name__,
|
|
|
|
plugin=klass,
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
if override:
|
|
|
|
# There was nothing already registered to override:
|
|
|
|
raise errors.PluginMissingOverrideError(
|
|
|
|
base=base.__name__,
|
|
|
|
name=klass.__name__,
|
|
|
|
plugin=klass,
|
|
|
|
)
|
|
|
|
|
|
|
|
# The plugin is okay, add to sub_d:
|
|
|
|
sub_d[klass.__name__] = klass
|
|
|
|
|
|
|
|
if not found:
|
|
|
|
raise errors.PluginSubclassError(
|
|
|
|
plugin=klass,
|
|
|
|
bases=self.__plugins.keys(),
|
|
|
|
)
|
|
|
|
|
2008-08-12 17:52:37 -05:00
|
|
|
def finalize(self):
|
2008-08-08 16:40:03 -05:00
|
|
|
"""
|
|
|
|
Finalize the registration, instantiate the plugins.
|
2008-10-27 00:53:44 -05:00
|
|
|
|
|
|
|
`API.bootstrap` will automatically be called if it hasn't been
|
|
|
|
already.
|
2008-08-08 16:40:03 -05:00
|
|
|
"""
|
2008-10-27 00:28:06 -05:00
|
|
|
self.__doing('finalize')
|
2008-10-31 13:29:59 -05:00
|
|
|
self.__do_if_not_done('load_plugins')
|
2008-08-14 17:13:42 -05:00
|
|
|
|
2015-06-24 10:14:54 -05:00
|
|
|
production_mode = is_production_mode(self)
|
2008-09-23 23:44:52 -05:00
|
|
|
plugins = {}
|
2015-06-24 10:14:54 -05:00
|
|
|
plugin_info = {}
|
2008-08-08 16:40:03 -05:00
|
|
|
|
2015-06-24 10:14:54 -05:00
|
|
|
for base, sub_d in self.__plugins.iteritems():
|
2015-02-16 07:11:38 -06:00
|
|
|
name = base.__name__
|
2015-06-24 10:14:54 -05:00
|
|
|
|
|
|
|
members = []
|
|
|
|
for klass in sub_d.itervalues():
|
|
|
|
try:
|
|
|
|
instance = plugins[klass]
|
|
|
|
except KeyError:
|
|
|
|
instance = plugins[klass] = klass()
|
|
|
|
members.append(instance)
|
|
|
|
plugin_info.setdefault(
|
|
|
|
'%s.%s' % (klass.__module__, klass.__name__),
|
|
|
|
[]).append(name)
|
|
|
|
|
|
|
|
namespace = NameSpace(members)
|
2011-09-12 16:13:10 -05:00
|
|
|
if not production_mode:
|
2011-01-19 10:24:31 -06:00
|
|
|
assert not (
|
|
|
|
name in self.__d or hasattr(self, name)
|
|
|
|
)
|
2008-08-14 17:13:42 -05:00
|
|
|
self.__d[name] = namespace
|
|
|
|
object.__setattr__(self, name, namespace)
|
|
|
|
|
2015-06-24 10:14:54 -05:00
|
|
|
for instance in plugins.itervalues():
|
|
|
|
instance.set_api(self)
|
2008-09-21 16:50:56 -05:00
|
|
|
|
2015-06-24 10:14:54 -05:00
|
|
|
for klass, instance in plugins.iteritems():
|
2011-09-12 16:13:10 -05:00
|
|
|
if not production_mode:
|
2015-06-24 10:14:54 -05:00
|
|
|
assert instance.api is self
|
|
|
|
if klass.finalize_early or not self.env.plugins_on_demand:
|
|
|
|
instance.ensure_finalized()
|
|
|
|
if not production_mode:
|
|
|
|
assert islocked(instance)
|
|
|
|
|
2008-08-12 18:40:36 -05:00
|
|
|
object.__setattr__(self, '_API__finalized', True)
|
2008-09-23 23:44:52 -05:00
|
|
|
object.__setattr__(self, 'plugins',
|
2015-06-24 10:14:54 -05:00
|
|
|
tuple((k, tuple(v)) for k, v in plugin_info.iteritems())
|
2008-09-23 23:44:52 -05:00
|
|
|
)
|
2012-11-06 10:05:41 -06:00
|
|
|
|
|
|
|
|
|
|
|
class IPAHelpFormatter(optparse.IndentedHelpFormatter):
|
|
|
|
def format_epilog(self, text):
|
|
|
|
text_width = self.width - self.current_indent
|
|
|
|
indent = " " * self.current_indent
|
|
|
|
lines = text.splitlines()
|
|
|
|
result = '\n'.join(
|
|
|
|
textwrap.fill(line, text_width, initial_indent=indent,
|
|
|
|
subsequent_indent=indent)
|
|
|
|
for line in lines)
|
|
|
|
return '\n%s\n' % result
|