Imported Upstream version 4.6.2
This commit is contained in:
7
ipapython/install/__init__.py
Normal file
7
ipapython/install/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
#
|
||||
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
"""
|
||||
Installer framework.
|
||||
"""
|
||||
359
ipapython/install/cli.py
Normal file
359
ipapython/install/cli.py
Normal file
@@ -0,0 +1,359 @@
|
||||
#
|
||||
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
"""
|
||||
Command line support.
|
||||
"""
|
||||
|
||||
import collections
|
||||
import enum
|
||||
import logging
|
||||
import optparse # pylint: disable=deprecated-module
|
||||
import signal
|
||||
|
||||
import six
|
||||
|
||||
from ipapython import admintool
|
||||
from ipapython.ipa_log_manager import standard_logging_setup
|
||||
from ipapython.ipautil import (CheckedIPAddress, CheckedIPAddressLoopback,
|
||||
private_ccache)
|
||||
|
||||
from . import core, common
|
||||
|
||||
__all__ = ['install_tool', 'uninstall_tool']
|
||||
|
||||
if six.PY3:
|
||||
long = int
|
||||
|
||||
NoneType = type(None)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_usage(configurable_class):
|
||||
usage = '%prog [options]'
|
||||
|
||||
for owner_cls, name in configurable_class.knobs():
|
||||
knob_cls = getattr(owner_cls, name)
|
||||
if knob_cls.is_cli_positional():
|
||||
if knob_cls.cli_metavar is not None:
|
||||
metavar = knob_cls.cli_metavar
|
||||
elif knob_cls.cli_names:
|
||||
metavar = knob_cls.cli_names[0].upper()
|
||||
else:
|
||||
metavar = name.replace('_', '-').upper()
|
||||
|
||||
try:
|
||||
knob_cls.default
|
||||
except AttributeError:
|
||||
fmt = ' {}'
|
||||
else:
|
||||
fmt = ' [{}]'
|
||||
|
||||
usage += fmt.format(metavar)
|
||||
|
||||
return usage
|
||||
|
||||
|
||||
def install_tool(configurable_class, command_name, log_file_name,
|
||||
debug_option=False, verbose=False, console_format=None,
|
||||
use_private_ccache=True, uninstall_log_file_name=None):
|
||||
if uninstall_log_file_name is not None:
|
||||
uninstall_kwargs = dict(
|
||||
configurable_class=configurable_class,
|
||||
command_name=command_name,
|
||||
log_file_name=uninstall_log_file_name,
|
||||
debug_option=debug_option,
|
||||
verbose=verbose,
|
||||
console_format=console_format,
|
||||
)
|
||||
else:
|
||||
uninstall_kwargs = None
|
||||
|
||||
return type(
|
||||
'install_tool({0})'.format(configurable_class.__name__),
|
||||
(InstallTool,),
|
||||
dict(
|
||||
configurable_class=configurable_class,
|
||||
command_name=command_name,
|
||||
log_file_name=log_file_name,
|
||||
usage=_get_usage(configurable_class),
|
||||
debug_option=debug_option,
|
||||
verbose=verbose,
|
||||
console_format=console_format,
|
||||
uninstall_kwargs=uninstall_kwargs,
|
||||
use_private_ccache=use_private_ccache,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def uninstall_tool(configurable_class, command_name, log_file_name,
|
||||
debug_option=False, verbose=False, console_format=None):
|
||||
return type(
|
||||
'uninstall_tool({0})'.format(configurable_class.__name__),
|
||||
(UninstallTool,),
|
||||
dict(
|
||||
configurable_class=configurable_class,
|
||||
command_name=command_name,
|
||||
log_file_name=log_file_name,
|
||||
usage=_get_usage(configurable_class),
|
||||
debug_option=debug_option,
|
||||
verbose=verbose,
|
||||
console_format=console_format,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ConfigureTool(admintool.AdminTool):
|
||||
configurable_class = None
|
||||
debug_option = False
|
||||
verbose = False
|
||||
console_format = None
|
||||
use_private_ccache = True
|
||||
|
||||
@staticmethod
|
||||
def _transform(configurable_class):
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def add_options(cls, parser, positional=False):
|
||||
transformed_cls = cls._transform(cls.configurable_class)
|
||||
|
||||
if issubclass(transformed_cls, common.Interactive):
|
||||
parser.add_option(
|
||||
'-U', '--unattended',
|
||||
dest='unattended',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help="unattended (un)installation never prompts the user",
|
||||
)
|
||||
|
||||
groups = collections.OrderedDict()
|
||||
# if no group is defined, add the option to the parser top level
|
||||
groups[None] = parser
|
||||
|
||||
for owner_cls, name in transformed_cls.knobs():
|
||||
knob_cls = getattr(owner_cls, name)
|
||||
if knob_cls.is_cli_positional() is not positional:
|
||||
continue
|
||||
|
||||
group_cls = knob_cls.group()
|
||||
try:
|
||||
opt_group = groups[group_cls]
|
||||
except KeyError:
|
||||
opt_group = groups[group_cls] = optparse.OptionGroup(
|
||||
parser, "{0} options".format(group_cls.description))
|
||||
parser.add_option_group(opt_group)
|
||||
|
||||
knob_type = knob_cls.type
|
||||
if issubclass(knob_type, list):
|
||||
try:
|
||||
# typing.List[X].__parameters__ == (X,)
|
||||
knob_scalar_type = knob_type.__parameters__[0]
|
||||
except AttributeError:
|
||||
knob_scalar_type = str
|
||||
else:
|
||||
knob_scalar_type = knob_type
|
||||
|
||||
kwargs = dict()
|
||||
if knob_scalar_type is NoneType:
|
||||
kwargs['type'] = None
|
||||
kwargs['const'] = True
|
||||
kwargs['default'] = False
|
||||
elif knob_scalar_type is str:
|
||||
kwargs['type'] = 'string'
|
||||
elif knob_scalar_type is int:
|
||||
kwargs['type'] = 'int'
|
||||
elif knob_scalar_type is long:
|
||||
kwargs['type'] = 'long'
|
||||
elif knob_scalar_type is CheckedIPAddressLoopback:
|
||||
kwargs['type'] = 'ip_with_loopback'
|
||||
elif knob_scalar_type is CheckedIPAddress:
|
||||
kwargs['type'] = 'ip'
|
||||
elif issubclass(knob_scalar_type, enum.Enum):
|
||||
kwargs['type'] = 'choice'
|
||||
kwargs['choices'] = [i.value for i in knob_scalar_type]
|
||||
kwargs['metavar'] = "{{{0}}}".format(
|
||||
",".join(kwargs['choices']))
|
||||
else:
|
||||
kwargs['type'] = 'constructor'
|
||||
kwargs['constructor'] = knob_scalar_type
|
||||
kwargs['dest'] = name
|
||||
if issubclass(knob_type, list):
|
||||
if kwargs['type'] is None:
|
||||
kwargs['action'] = 'append_const'
|
||||
else:
|
||||
kwargs['action'] = 'append'
|
||||
else:
|
||||
if kwargs['type'] is None:
|
||||
kwargs['action'] = 'store_const'
|
||||
else:
|
||||
kwargs['action'] = 'store'
|
||||
if knob_cls.sensitive:
|
||||
kwargs['sensitive'] = True
|
||||
if knob_cls.cli_metavar:
|
||||
kwargs['metavar'] = knob_cls.cli_metavar
|
||||
|
||||
if not positional:
|
||||
cli_info = (
|
||||
(knob_cls.deprecated, knob_cls.cli_names),
|
||||
(True, knob_cls.cli_deprecated_names),
|
||||
)
|
||||
else:
|
||||
cli_info = (
|
||||
(knob_cls.deprecated, (None,)),
|
||||
)
|
||||
for hidden, cli_names in cli_info:
|
||||
opt_strs = []
|
||||
for cli_name in cli_names:
|
||||
if cli_name is None:
|
||||
cli_name = '--{}'.format(name.replace('_', '-'))
|
||||
opt_strs.append(cli_name)
|
||||
if not opt_strs:
|
||||
continue
|
||||
|
||||
if not hidden:
|
||||
help = knob_cls.description
|
||||
else:
|
||||
help = optparse.SUPPRESS_HELP
|
||||
|
||||
opt_group.add_option(
|
||||
*opt_strs,
|
||||
help=help,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
super(ConfigureTool, cls).add_options(parser,
|
||||
debug_option=cls.debug_option)
|
||||
|
||||
def __init__(self, options, args):
|
||||
super(ConfigureTool, self).__init__(options, args)
|
||||
|
||||
self.transformed_cls = self._transform(self.configurable_class)
|
||||
self.positional_arguments = []
|
||||
|
||||
for owner_cls, name in self.transformed_cls.knobs():
|
||||
knob_cls = getattr(owner_cls, name)
|
||||
if knob_cls.is_cli_positional():
|
||||
self.positional_arguments.append(name)
|
||||
|
||||
# fake option parser to parse positional arguments
|
||||
# (because optparse does not support positional argument parsing)
|
||||
fake_option_parser = optparse.OptionParser()
|
||||
self.add_options(fake_option_parser, True)
|
||||
|
||||
fake_option_map = {option.dest: option
|
||||
for group in fake_option_parser.option_groups
|
||||
for option in group.option_list}
|
||||
|
||||
for index, name in enumerate(self.positional_arguments):
|
||||
try:
|
||||
value = self.args.pop(0)
|
||||
except IndexError:
|
||||
break
|
||||
|
||||
fake_option = fake_option_map[name]
|
||||
fake_option.process('argument {}'.format(index + 1),
|
||||
value,
|
||||
self.options,
|
||||
self.option_parser)
|
||||
|
||||
def validate_options(self, needs_root=True):
|
||||
super(ConfigureTool, self).validate_options(needs_root=needs_root)
|
||||
|
||||
if self.args:
|
||||
self.option_parser.error("Too many arguments provided")
|
||||
|
||||
def _setup_logging(self, log_file_mode='w', no_file=False):
|
||||
if no_file:
|
||||
log_file_name = None
|
||||
elif self.options.log_file:
|
||||
log_file_name = self.options.log_file
|
||||
else:
|
||||
log_file_name = self.log_file_name
|
||||
standard_logging_setup(
|
||||
log_file_name,
|
||||
verbose=self.verbose,
|
||||
debug=self.options.verbose,
|
||||
console_format=self.console_format)
|
||||
if log_file_name:
|
||||
logger.debug('Logging to %s', log_file_name)
|
||||
elif not no_file:
|
||||
logger.debug('Not logging to a file')
|
||||
|
||||
def run(self):
|
||||
kwargs = {}
|
||||
|
||||
transformed_cls = self._transform(self.configurable_class)
|
||||
knob_classes = {n: getattr(c, n) for c, n in transformed_cls.knobs()}
|
||||
for name in knob_classes:
|
||||
value = getattr(self.options, name, None)
|
||||
if value is not None:
|
||||
kwargs[name] = value
|
||||
|
||||
if (issubclass(self.configurable_class, common.Interactive) and
|
||||
not self.options.unattended):
|
||||
kwargs['interactive'] = True
|
||||
|
||||
try:
|
||||
cfgr = transformed_cls(**kwargs)
|
||||
except core.KnobValueError as e:
|
||||
knob_cls = knob_classes[e.name]
|
||||
try:
|
||||
index = self.positional_arguments.index(e.name)
|
||||
except ValueError:
|
||||
cli_name = knob_cls.cli_names[0] or e.name.replace('_', '-')
|
||||
desc = "option {0}".format(cli_name)
|
||||
else:
|
||||
desc = "argument {0}".format(index + 1)
|
||||
self.option_parser.error("{0}: {1}".format(desc, e))
|
||||
except RuntimeError as e:
|
||||
self.option_parser.error(str(e))
|
||||
|
||||
signal.signal(signal.SIGTERM, self.__signal_handler)
|
||||
|
||||
if self.use_private_ccache:
|
||||
with private_ccache():
|
||||
super(ConfigureTool, self).run()
|
||||
cfgr.run()
|
||||
else:
|
||||
super(ConfigureTool, self).run()
|
||||
cfgr.run()
|
||||
|
||||
@staticmethod
|
||||
def __signal_handler(signum, frame):
|
||||
raise KeyboardInterrupt
|
||||
|
||||
|
||||
class InstallTool(ConfigureTool):
|
||||
uninstall_kwargs = None
|
||||
|
||||
_transform = staticmethod(common.installer)
|
||||
|
||||
@classmethod
|
||||
def add_options(cls, parser, positional=False):
|
||||
super(InstallTool, cls).add_options(parser, positional)
|
||||
|
||||
if cls.uninstall_kwargs is not None:
|
||||
parser.add_option(
|
||||
'--uninstall',
|
||||
dest='uninstall',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help=("uninstall an existing installation. The uninstall can "
|
||||
"be run with --unattended option"),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_command_class(cls, options, args):
|
||||
if cls.uninstall_kwargs is not None and options.uninstall:
|
||||
uninstall_cls = uninstall_tool(**cls.uninstall_kwargs)
|
||||
uninstall_cls.option_parser = cls.option_parser
|
||||
return uninstall_cls
|
||||
else:
|
||||
return super(InstallTool, cls).get_command_class(options, args)
|
||||
|
||||
|
||||
class UninstallTool(ConfigureTool):
|
||||
_transform = staticmethod(common.uninstaller)
|
||||
118
ipapython/install/common.py
Normal file
118
ipapython/install/common.py
Normal file
@@ -0,0 +1,118 @@
|
||||
#
|
||||
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
"""
|
||||
Common stuff.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
from . import core
|
||||
from .util import from_
|
||||
|
||||
__all__ = ['step', 'Installable', 'Interactive', 'Continuous', 'installer',
|
||||
'uninstaller']
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def step():
|
||||
def decorator(func):
|
||||
cls = core.Component(Step)
|
||||
cls._installer = staticmethod(func)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class Installable(core.Configurable):
|
||||
"""
|
||||
Configurable which does install or uninstall.
|
||||
"""
|
||||
|
||||
uninstalling = core.Property(False)
|
||||
|
||||
def _get_components(self):
|
||||
components = super(Installable, self)._get_components()
|
||||
if self.uninstalling:
|
||||
components = reversed(list(components))
|
||||
return components
|
||||
|
||||
def _configure(self):
|
||||
if self.uninstalling:
|
||||
return self._uninstall()
|
||||
else:
|
||||
return self._install()
|
||||
|
||||
def _install(self):
|
||||
assert not hasattr(super(Installable, self), '_install')
|
||||
|
||||
return super(Installable, self)._configure()
|
||||
|
||||
def _uninstall(self):
|
||||
assert not hasattr(super(Installable, self), '_uninstall')
|
||||
|
||||
return super(Installable, self)._configure()
|
||||
|
||||
|
||||
class Step(Installable):
|
||||
@property
|
||||
def parent(self):
|
||||
raise AttributeError('parent')
|
||||
|
||||
def _install(self):
|
||||
for _nothing in self._installer(self.parent):
|
||||
yield from_(super(Step, self)._install())
|
||||
|
||||
@staticmethod
|
||||
def _installer(obj):
|
||||
yield
|
||||
|
||||
def _uninstall(self):
|
||||
for _nothing in self._uninstaller(self.parent):
|
||||
yield from_(super(Step, self)._uninstall())
|
||||
|
||||
@staticmethod
|
||||
def _uninstaller(obj):
|
||||
yield
|
||||
|
||||
@classmethod
|
||||
def uninstaller(cls, func):
|
||||
cls._uninstaller = staticmethod(func)
|
||||
return cls
|
||||
|
||||
|
||||
class Interactive(core.Configurable):
|
||||
interactive = core.Property(False)
|
||||
|
||||
|
||||
class Continuous(core.Configurable):
|
||||
def _handle_execute_exception(self, exc_info):
|
||||
try:
|
||||
super(Continuous, self)._handle_execute_exception(exc_info)
|
||||
except BaseException as e:
|
||||
logger.debug("%s", traceback.format_exc())
|
||||
if isinstance(e, Exception):
|
||||
logger.error("%s", e)
|
||||
|
||||
|
||||
def installer(cls):
|
||||
class Installer(cls, Installable):
|
||||
def __init__(self, **kwargs):
|
||||
super(Installer, self).__init__(uninstalling=False,
|
||||
**kwargs)
|
||||
Installer.__name__ = 'installer({0})'.format(cls.__name__)
|
||||
|
||||
return Installer
|
||||
|
||||
|
||||
def uninstaller(cls):
|
||||
class Uninstaller(Continuous, cls, Installable):
|
||||
def __init__(self, **kwargs):
|
||||
super(Uninstaller, self).__init__(uninstalling=True,
|
||||
**kwargs)
|
||||
Uninstaller.__name__ = 'uninstaller({0})'.format(cls.__name__)
|
||||
|
||||
return Uninstaller
|
||||
663
ipapython/install/core.py
Normal file
663
ipapython/install/core.py
Normal file
@@ -0,0 +1,663 @@
|
||||
#
|
||||
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
"""
|
||||
The framework core.
|
||||
"""
|
||||
|
||||
import abc
|
||||
import collections
|
||||
import functools
|
||||
import itertools
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
from . import util
|
||||
from .util import from_
|
||||
|
||||
__all__ = ['InvalidStateError', 'KnobValueError', 'Property', 'knob',
|
||||
'Configurable', 'group', 'Component', 'Composite']
|
||||
|
||||
NoneType = type(None)
|
||||
builtin_type = type
|
||||
|
||||
# Configurable states
|
||||
_VALIDATE_PENDING = 'VALIDATE_PENDING'
|
||||
_VALIDATE_RUNNING = 'VALIDATE_RUNNING'
|
||||
_EXECUTE_PENDING = 'EXECUTE_PENDING'
|
||||
_EXECUTE_RUNNING = 'EXECUTE_RUNNING'
|
||||
_STOPPED = 'STOPPED'
|
||||
_FAILED = 'FAILED'
|
||||
_CLOSED = 'CLOSED'
|
||||
|
||||
_missing = object()
|
||||
_counter = itertools.count()
|
||||
|
||||
|
||||
@functools.cmp_to_key
|
||||
def _class_key(a, b):
|
||||
if a is b:
|
||||
return 0
|
||||
elif issubclass(a, b):
|
||||
return -1
|
||||
elif issubclass(b, a):
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
class InvalidStateError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class KnobValueError(ValueError):
|
||||
def __init__(self, name, message):
|
||||
super(KnobValueError, self).__init__(message)
|
||||
self.name = name
|
||||
|
||||
|
||||
class PropertyBase(six.with_metaclass(util.InnerClassMeta, object)):
|
||||
# shut up pylint
|
||||
__outer_class__ = None
|
||||
__outer_name__ = None
|
||||
|
||||
_order = None
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
raise AttributeError('default')
|
||||
|
||||
def __init__(self, outer):
|
||||
pass
|
||||
|
||||
def __get__(self, obj, obj_type):
|
||||
while obj is not None:
|
||||
try:
|
||||
return obj.__dict__[self.__outer_name__]
|
||||
except KeyError:
|
||||
pass
|
||||
obj = obj._get_fallback()
|
||||
|
||||
try:
|
||||
return self.default
|
||||
except AttributeError:
|
||||
raise AttributeError(self.__outer_name__)
|
||||
|
||||
def __set__(self, obj, value):
|
||||
try:
|
||||
obj.__dict__[self.__outer_name__] = value
|
||||
except KeyError:
|
||||
raise AttributeError(self.__outer_name__)
|
||||
|
||||
def __delete__(self, obj):
|
||||
try:
|
||||
del obj.__dict__[self.__outer_name__]
|
||||
except KeyError:
|
||||
raise AttributeError(self.__outer_name__)
|
||||
|
||||
|
||||
def Property(default=_missing):
|
||||
class_dict = {}
|
||||
if default is not _missing:
|
||||
class_dict['default'] = default
|
||||
|
||||
return util.InnerClassMeta('Property', (PropertyBase,), class_dict)
|
||||
|
||||
|
||||
class KnobBase(PropertyBase):
|
||||
type = None
|
||||
sensitive = False
|
||||
deprecated = False
|
||||
description = None
|
||||
cli_names = (None,)
|
||||
cli_deprecated_names = ()
|
||||
cli_metavar = None
|
||||
|
||||
def __init__(self, outer):
|
||||
self.outer = outer
|
||||
|
||||
def validate(self, value):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def group(cls):
|
||||
return cls.__outer_class__.group()
|
||||
|
||||
@classmethod
|
||||
def is_cli_positional(cls):
|
||||
return all(n is not None and not n.startswith('-')
|
||||
for n in cls.cli_names)
|
||||
|
||||
@classmethod
|
||||
def default_getter(cls, func):
|
||||
@property
|
||||
def default(self):
|
||||
return func(self.outer)
|
||||
cls.default = default
|
||||
|
||||
return cls
|
||||
|
||||
@classmethod
|
||||
def validator(cls, func):
|
||||
def validate(self, value):
|
||||
func(self.outer, value)
|
||||
super(cls, self).validate(value)
|
||||
cls.validate = validate
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
def _knob(type=_missing, default=_missing, bases=_missing, _order=_missing,
|
||||
sensitive=_missing, deprecated=_missing, description=_missing,
|
||||
group=_missing, cli_names=_missing, cli_deprecated_names=_missing,
|
||||
cli_metavar=_missing):
|
||||
if type is None:
|
||||
type = NoneType
|
||||
|
||||
if bases is _missing:
|
||||
bases = (KnobBase,)
|
||||
elif isinstance(bases, builtin_type):
|
||||
bases = (bases,)
|
||||
|
||||
if cli_names is None or isinstance(cli_names, str):
|
||||
cli_names = (cli_names,)
|
||||
elif cli_names is not _missing:
|
||||
cli_names = tuple(cli_names)
|
||||
|
||||
if isinstance(cli_deprecated_names, str):
|
||||
cli_deprecated_names = (cli_deprecated_names,)
|
||||
elif cli_deprecated_names is not _missing:
|
||||
cli_deprecated_names = tuple(cli_deprecated_names)
|
||||
|
||||
class_dict = {}
|
||||
if type is not _missing:
|
||||
class_dict['type'] = type
|
||||
if default is not _missing:
|
||||
class_dict['default'] = default
|
||||
if _order is not _missing:
|
||||
class_dict['_order'] = _order
|
||||
if sensitive is not _missing:
|
||||
class_dict['sensitive'] = sensitive
|
||||
if deprecated is not _missing:
|
||||
class_dict['deprecated'] = deprecated
|
||||
if description is not _missing:
|
||||
class_dict['description'] = description
|
||||
if group is not _missing:
|
||||
class_dict['group'] = group
|
||||
if cli_names is not _missing:
|
||||
class_dict['cli_names'] = cli_names
|
||||
if cli_deprecated_names is not _missing:
|
||||
class_dict['cli_deprecated_names'] = cli_deprecated_names
|
||||
if cli_metavar is not _missing:
|
||||
class_dict['cli_metavar'] = cli_metavar
|
||||
|
||||
return util.InnerClassMeta('Knob', bases, class_dict)
|
||||
|
||||
|
||||
def knob(type, default=_missing, **kwargs):
|
||||
"""
|
||||
Define a new knob.
|
||||
"""
|
||||
return _knob(
|
||||
type, default,
|
||||
_order=next(_counter),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
def extend_knob(base, default=_missing, bases=_missing, group=_missing,
|
||||
**kwargs):
|
||||
"""
|
||||
Extend an existing knob.
|
||||
"""
|
||||
if bases is _missing:
|
||||
bases = (base,)
|
||||
|
||||
if group is _missing:
|
||||
group = staticmethod(base.group)
|
||||
|
||||
return _knob(
|
||||
_missing, default,
|
||||
bases=bases,
|
||||
_order=_missing,
|
||||
group=group,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
class Configurable(six.with_metaclass(abc.ABCMeta, object)):
|
||||
"""
|
||||
Base class of all configurables.
|
||||
|
||||
FIXME: details of validate/execute, properties and knobs
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def properties(cls):
|
||||
"""
|
||||
Iterate over properties defined for the configurable.
|
||||
"""
|
||||
|
||||
assert not hasattr(super(Configurable, cls), 'properties')
|
||||
|
||||
seen = set()
|
||||
|
||||
for owner_cls in cls.__mro__:
|
||||
result = []
|
||||
|
||||
for name, prop_cls in owner_cls.__dict__.items():
|
||||
if name in seen:
|
||||
continue
|
||||
seen.add(name)
|
||||
|
||||
if not isinstance(prop_cls, type):
|
||||
continue
|
||||
if not issubclass(prop_cls, PropertyBase):
|
||||
continue
|
||||
|
||||
result.append((prop_cls._order, owner_cls, name))
|
||||
|
||||
result = sorted(result, key=lambda r: r[0])
|
||||
|
||||
for _order, owner_cls, name in result:
|
||||
yield owner_cls, name
|
||||
|
||||
@classmethod
|
||||
def knobs(cls):
|
||||
for owner_cls, name in cls.properties():
|
||||
prop_cls = getattr(owner_cls, name)
|
||||
if issubclass(prop_cls, KnobBase):
|
||||
yield owner_cls, name
|
||||
|
||||
@classmethod
|
||||
def group(cls):
|
||||
assert not hasattr(super(Configurable, cls), 'group')
|
||||
|
||||
return None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
Initialize the configurable.
|
||||
"""
|
||||
|
||||
cls = self.__class__
|
||||
for owner_cls, name in cls.properties():
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
prop_cls = getattr(owner_cls, name)
|
||||
if not isinstance(prop_cls, type):
|
||||
continue
|
||||
if not issubclass(prop_cls, PropertyBase):
|
||||
continue
|
||||
|
||||
try:
|
||||
value = kwargs.pop(name)
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
setattr(self, name, value)
|
||||
|
||||
for owner_cls, name in cls.knobs():
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
if not isinstance(self, owner_cls):
|
||||
continue
|
||||
value = getattr(self, name, None)
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
prop_cls = getattr(owner_cls, name)
|
||||
prop = prop_cls(self)
|
||||
try:
|
||||
prop.validate(value)
|
||||
except KnobValueError:
|
||||
raise
|
||||
except ValueError as e:
|
||||
raise KnobValueError(name, str(e))
|
||||
|
||||
if kwargs:
|
||||
extra = sorted(kwargs)
|
||||
raise TypeError(
|
||||
"{0}() got {1} unexpected keyword arguments: {2}".format(
|
||||
type(self).__name__,
|
||||
len(extra),
|
||||
', '.join(repr(name) for name in extra)))
|
||||
|
||||
self._reset()
|
||||
|
||||
def _reset(self):
|
||||
assert not hasattr(super(Configurable, self), '_reset')
|
||||
|
||||
self.__state = _VALIDATE_PENDING
|
||||
self.__gen = util.run_generator_with_yield_from(self._configure())
|
||||
|
||||
def _get_components(self):
|
||||
assert not hasattr(super(Configurable, self), '_get_components')
|
||||
|
||||
raise TypeError("{0} is not composite".format(self))
|
||||
|
||||
def _get_fallback(self):
|
||||
return None
|
||||
|
||||
@abc.abstractmethod
|
||||
def _configure(self):
|
||||
"""
|
||||
Coroutine which defines the logic of the configurable.
|
||||
"""
|
||||
|
||||
assert not hasattr(super(Configurable, self), '_configure')
|
||||
|
||||
self.__transition(_VALIDATE_RUNNING, _EXECUTE_PENDING)
|
||||
|
||||
while self.__state != _EXECUTE_RUNNING:
|
||||
yield
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Run the configurable.
|
||||
"""
|
||||
|
||||
self.validate()
|
||||
if self.__state == _EXECUTE_PENDING:
|
||||
self.execute()
|
||||
|
||||
def validate(self):
|
||||
"""
|
||||
Run the validation part of the configurable.
|
||||
"""
|
||||
|
||||
for _nothing in self._validator():
|
||||
pass
|
||||
|
||||
def _validator(self):
|
||||
"""
|
||||
Coroutine which runs the validation part of the configurable.
|
||||
"""
|
||||
|
||||
return self.__runner(_VALIDATE_PENDING,
|
||||
_VALIDATE_RUNNING,
|
||||
self._handle_validate_exception)
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
Run the execution part of the configurable.
|
||||
"""
|
||||
|
||||
for _nothing in self._executor():
|
||||
pass
|
||||
|
||||
def _executor(self):
|
||||
"""
|
||||
Coroutine which runs the execution part of the configurable.
|
||||
"""
|
||||
|
||||
return self.__runner(_EXECUTE_PENDING,
|
||||
_EXECUTE_RUNNING,
|
||||
self._handle_execute_exception)
|
||||
|
||||
def done(self):
|
||||
"""
|
||||
Return True if the configurable has finished.
|
||||
"""
|
||||
|
||||
return self.__state in (_STOPPED, _FAILED, _CLOSED)
|
||||
|
||||
def run_until_executing(self, gen):
|
||||
while self.__state != _EXECUTE_RUNNING:
|
||||
try:
|
||||
yield next(gen)
|
||||
except StopIteration:
|
||||
break
|
||||
|
||||
def __runner(self, pending_state, running_state, exc_handler):
|
||||
self.__transition(pending_state, running_state)
|
||||
|
||||
step = lambda: next(self.__gen)
|
||||
while True:
|
||||
try:
|
||||
step()
|
||||
except StopIteration:
|
||||
self.__transition(running_state, _STOPPED)
|
||||
break
|
||||
except GeneratorExit:
|
||||
self.__transition(running_state, _CLOSED)
|
||||
break
|
||||
except BaseException:
|
||||
exc_info = sys.exc_info()
|
||||
try:
|
||||
exc_handler(exc_info)
|
||||
except BaseException:
|
||||
self.__transition(running_state, _FAILED)
|
||||
raise
|
||||
|
||||
if self.__state != running_state:
|
||||
break
|
||||
|
||||
try:
|
||||
yield
|
||||
except BaseException:
|
||||
exc_info = sys.exc_info()
|
||||
step = lambda: self.__gen.throw(*exc_info)
|
||||
else:
|
||||
step = lambda: next(self.__gen)
|
||||
|
||||
def _handle_exception(self, exc_info):
|
||||
assert not hasattr(super(Configurable, self), '_handle_exception')
|
||||
|
||||
six.reraise(*exc_info)
|
||||
|
||||
def _handle_validate_exception(self, exc_info):
|
||||
assert not hasattr(super(Configurable, self),
|
||||
'_handle_validate_exception')
|
||||
self._handle_exception(exc_info)
|
||||
|
||||
def _handle_execute_exception(self, exc_info):
|
||||
assert not hasattr(super(Configurable, self),
|
||||
'_handle_execute_exception')
|
||||
self._handle_exception(exc_info)
|
||||
|
||||
def __transition(self, from_state, to_state):
|
||||
if self.__state != from_state:
|
||||
raise InvalidStateError(self.__state)
|
||||
|
||||
self.__state = to_state
|
||||
|
||||
|
||||
def group(cls):
|
||||
def group():
|
||||
return cls
|
||||
|
||||
cls.group = staticmethod(group)
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
class ComponentMeta(util.InnerClassMeta, abc.ABCMeta):
|
||||
pass
|
||||
|
||||
|
||||
class ComponentBase(six.with_metaclass(ComponentMeta, Configurable)):
|
||||
# shut up pylint
|
||||
__outer_class__ = None
|
||||
__outer_name__ = None
|
||||
|
||||
_order = None
|
||||
|
||||
@classmethod
|
||||
def group(cls):
|
||||
result = super(ComponentBase, cls).group()
|
||||
if result is not None:
|
||||
return result
|
||||
else:
|
||||
return cls.__outer_class__.group()
|
||||
|
||||
def __init__(self, parent, **kwargs):
|
||||
self.__parent = parent
|
||||
|
||||
super(ComponentBase, self).__init__(**kwargs)
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self.__parent
|
||||
|
||||
def __get__(self, obj, obj_type):
|
||||
obj.__dict__[self.__outer_name__] = self
|
||||
return self
|
||||
|
||||
def _get_fallback(self):
|
||||
return self.__parent
|
||||
|
||||
def _handle_exception(self, exc_info):
|
||||
try:
|
||||
super(ComponentBase, self)._handle_exception(exc_info)
|
||||
except BaseException:
|
||||
exc_info = sys.exc_info()
|
||||
self.__parent._handle_exception(exc_info)
|
||||
|
||||
|
||||
def Component(cls):
|
||||
class_dict = {}
|
||||
class_dict['_order'] = next(_counter)
|
||||
|
||||
return ComponentMeta('Component', (ComponentBase, cls), class_dict)
|
||||
|
||||
|
||||
class Composite(Configurable):
|
||||
"""
|
||||
Configurable composed of any number of components.
|
||||
|
||||
Provides knobs of all child components.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def properties(cls):
|
||||
name_dict = {}
|
||||
owner_dict = collections.OrderedDict()
|
||||
|
||||
for owner_cls, name in super(Composite, cls).properties():
|
||||
name_dict[name] = owner_cls
|
||||
owner_dict.setdefault(owner_cls, []).append(name)
|
||||
|
||||
for owner_cls, name in cls.components():
|
||||
comp_cls = getattr(cls, name)
|
||||
|
||||
for owner_cls, name in comp_cls.knobs():
|
||||
if hasattr(cls, name):
|
||||
continue
|
||||
|
||||
try:
|
||||
last_owner_cls = name_dict[name]
|
||||
except KeyError:
|
||||
name_dict[name] = owner_cls
|
||||
owner_dict.setdefault(owner_cls, []).append(name)
|
||||
else:
|
||||
knob_cls = getattr(owner_cls, name)
|
||||
last_knob_cls = getattr(last_owner_cls, name)
|
||||
if issubclass(knob_cls, last_knob_cls):
|
||||
name_dict[name] = owner_cls
|
||||
owner_dict[last_owner_cls].remove(name)
|
||||
owner_dict.setdefault(owner_cls, [])
|
||||
if name not in owner_dict[owner_cls]:
|
||||
owner_dict[owner_cls].append(name)
|
||||
elif not issubclass(last_knob_cls, knob_cls):
|
||||
raise TypeError("{0}.knobs(): conflicting definitions "
|
||||
"of '{1}' in {2} and {3}".format(
|
||||
cls.__name__,
|
||||
name,
|
||||
last_owner_cls.__name__,
|
||||
owner_cls.__name__))
|
||||
|
||||
for owner_cls in sorted(owner_dict, key=_class_key):
|
||||
for name in owner_dict[owner_cls]:
|
||||
yield owner_cls, name
|
||||
|
||||
@classmethod
|
||||
def components(cls):
|
||||
assert not hasattr(super(Composite, cls), 'components')
|
||||
|
||||
seen = set()
|
||||
|
||||
for owner_cls in cls.__mro__:
|
||||
result = []
|
||||
|
||||
for name, comp_cls in owner_cls.__dict__.items():
|
||||
if name in seen:
|
||||
continue
|
||||
seen.add(name)
|
||||
|
||||
if not isinstance(comp_cls, type):
|
||||
continue
|
||||
if not issubclass(comp_cls, ComponentBase):
|
||||
continue
|
||||
|
||||
result.append((comp_cls._order, owner_cls, name))
|
||||
|
||||
result = sorted(result, key=lambda r: r[0])
|
||||
|
||||
for _order, owner_cls, name in result:
|
||||
yield owner_cls, name
|
||||
|
||||
def __getattr__(self, name):
|
||||
for owner_cls, knob_name in self.knobs():
|
||||
if knob_name == name:
|
||||
break
|
||||
else:
|
||||
raise AttributeError(name)
|
||||
|
||||
for component in self.__components:
|
||||
if isinstance(component, owner_cls):
|
||||
break
|
||||
else:
|
||||
raise AttributeError(name)
|
||||
|
||||
return getattr(component, name)
|
||||
|
||||
def _reset(self):
|
||||
self.__components = list(self._get_components())
|
||||
|
||||
super(Composite, self)._reset()
|
||||
|
||||
def _get_components(self):
|
||||
for _owner_cls, name in self.components():
|
||||
yield getattr(self, name)
|
||||
|
||||
def _configure(self):
|
||||
validate = [(c, c._validator()) for c in self.__components]
|
||||
while True:
|
||||
new_validate = []
|
||||
for child, validator in validate:
|
||||
try:
|
||||
next(validator)
|
||||
except StopIteration:
|
||||
pass
|
||||
else:
|
||||
new_validate.append((child, validator))
|
||||
if not new_validate:
|
||||
break
|
||||
validate = new_validate
|
||||
|
||||
yield
|
||||
|
||||
if not self.__components:
|
||||
return
|
||||
|
||||
yield from_(super(Composite, self)._configure())
|
||||
|
||||
execute = [(c, c._executor()) for c in self.__components
|
||||
if not c.done()]
|
||||
while True:
|
||||
new_execute = []
|
||||
for child, executor in execute:
|
||||
try:
|
||||
next(executor)
|
||||
except StopIteration:
|
||||
pass
|
||||
else:
|
||||
new_execute.append((child, executor))
|
||||
if not new_execute:
|
||||
break
|
||||
execute = new_execute
|
||||
|
||||
yield
|
||||
34
ipapython/install/typing.py
Normal file
34
ipapython/install/typing.py
Normal file
@@ -0,0 +1,34 @@
|
||||
#
|
||||
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
import weakref
|
||||
|
||||
import six
|
||||
|
||||
_cache = weakref.WeakValueDictionary()
|
||||
|
||||
|
||||
class ListMeta(type):
|
||||
def __getitem__(cls, key):
|
||||
if not isinstance(key, type):
|
||||
raise TypeError("Parameters to generic types must be types. "
|
||||
"Got {!r}.".format(key))
|
||||
|
||||
t = ListMeta(
|
||||
cls.__name__,
|
||||
cls.__bases__,
|
||||
{
|
||||
'__parameters__': (key,),
|
||||
'__init__': cls.__init__,
|
||||
}
|
||||
)
|
||||
|
||||
return _cache.get(key, t)
|
||||
|
||||
|
||||
class List(six.with_metaclass(ListMeta, list)):
|
||||
__parameters__ = ()
|
||||
|
||||
def __init__(self, *_args, **_kwargs):
|
||||
raise TypeError("Type List cannot be instantiated; use list() instead")
|
||||
165
ipapython/install/util.py
Normal file
165
ipapython/install/util.py
Normal file
@@ -0,0 +1,165 @@
|
||||
#
|
||||
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
"""
|
||||
Utilities.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
|
||||
class from_(object):
|
||||
"""
|
||||
Wrapper for delegating to a subgenerator.
|
||||
|
||||
See `run_generator_with_yield_from`.
|
||||
"""
|
||||
__slots__ = ('obj',)
|
||||
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
|
||||
|
||||
def run_generator_with_yield_from(gen):
|
||||
"""
|
||||
Iterate over a generator object with subgenerator delegation.
|
||||
|
||||
This implements Python 3's ``yield from`` expressions, using Python 2
|
||||
syntax:
|
||||
|
||||
>>> def subgen():
|
||||
... yield 'B'
|
||||
... yield 'C'
|
||||
...
|
||||
>>> def gen():
|
||||
... yield 'A'
|
||||
... yield from_(subgen())
|
||||
... yield 'D'
|
||||
...
|
||||
>>> list(run_generator_with_yield_from(gen()))
|
||||
['A', 'B', 'C', 'D']
|
||||
|
||||
Returning value from a subgenerator is not supported.
|
||||
"""
|
||||
|
||||
exc_info = None
|
||||
value = None
|
||||
|
||||
stack = [gen]
|
||||
while stack:
|
||||
prev_exc_info, exc_info = exc_info, None
|
||||
prev_value, value = value, None
|
||||
|
||||
gen = stack[-1]
|
||||
try:
|
||||
if prev_exc_info is None:
|
||||
value = gen.send(prev_value)
|
||||
else:
|
||||
value = gen.throw(*prev_exc_info)
|
||||
except StopIteration:
|
||||
stack.pop()
|
||||
continue
|
||||
except BaseException:
|
||||
exc_info = sys.exc_info()
|
||||
stack.pop()
|
||||
continue
|
||||
else:
|
||||
if isinstance(value, from_):
|
||||
stack.append(value.obj)
|
||||
value = None
|
||||
continue
|
||||
|
||||
try:
|
||||
value = (yield value)
|
||||
except BaseException:
|
||||
exc_info = sys.exc_info()
|
||||
|
||||
if exc_info is not None:
|
||||
six.reraise(*exc_info)
|
||||
|
||||
|
||||
class InnerClassMeta(type):
|
||||
# pylint: disable=no-value-for-parameter
|
||||
def __new__(mcs, name, bases, class_dict):
|
||||
class_dict.pop('__outer_class__', None)
|
||||
class_dict.pop('__outer_name__', None)
|
||||
|
||||
return super(InnerClassMeta, mcs).__new__(mcs, name, bases, class_dict)
|
||||
|
||||
def __get__(cls, obj, obj_type):
|
||||
outer_class, outer_name = cls.__bind(obj_type)
|
||||
if obj is None:
|
||||
return cls
|
||||
assert isinstance(obj, outer_class)
|
||||
|
||||
try:
|
||||
return obj.__dict__[outer_name]
|
||||
except KeyError:
|
||||
inner = cls(obj)
|
||||
try:
|
||||
getter = inner.__get__
|
||||
except AttributeError:
|
||||
return inner
|
||||
else:
|
||||
return getter(obj, obj_type)
|
||||
|
||||
def __set__(cls, obj, value):
|
||||
outer_class, outer_name = cls.__bind(obj.__class__)
|
||||
assert isinstance(obj, outer_class)
|
||||
|
||||
inner = cls(obj)
|
||||
try:
|
||||
setter = inner.__set__
|
||||
except AttributeError:
|
||||
try:
|
||||
inner.__delete__
|
||||
except AttributeError:
|
||||
obj.__dict__[outer_name] = value
|
||||
else:
|
||||
raise AttributeError('__set__')
|
||||
else:
|
||||
setter(obj, value)
|
||||
|
||||
def __delete__(cls, obj):
|
||||
outer_class, outer_name = cls.__bind(obj.__class__)
|
||||
assert isinstance(obj, outer_class)
|
||||
|
||||
inner = cls(obj)
|
||||
try:
|
||||
deleter = inner.__delete__
|
||||
except AttributeError:
|
||||
try:
|
||||
inner.__set__
|
||||
except AttributeError:
|
||||
try:
|
||||
del obj.__dict__[outer_name]
|
||||
except KeyError:
|
||||
raise AttributeError(outer_name)
|
||||
else:
|
||||
raise AttributeError('__delete__')
|
||||
else:
|
||||
deleter(obj)
|
||||
|
||||
def __bind(cls, obj_type):
|
||||
try:
|
||||
outer_class = cls.__dict__['__outer_class__']
|
||||
name = cls.__dict__['__outer_name__']
|
||||
except KeyError:
|
||||
outer_class, name, value = None, None, None
|
||||
for outer_class in obj_type.__mro__:
|
||||
for name, value in six.iteritems(outer_class.__dict__):
|
||||
if value is cls:
|
||||
break
|
||||
if value is cls:
|
||||
break
|
||||
assert value is cls
|
||||
|
||||
cls.__outer_class__ = outer_class
|
||||
cls.__outer_name__ = name
|
||||
cls.__name__ = '.'.join((outer_class.__name__, name))
|
||||
cls.__qualname__ = cls.__name__
|
||||
|
||||
return outer_class, name
|
||||
Reference in New Issue
Block a user