2015-06-02 07:04:25 -05:00
|
|
|
#
|
|
|
|
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
|
|
|
|
#
|
|
|
|
|
|
|
|
"""
|
|
|
|
Command line support.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import collections
|
|
|
|
import optparse
|
|
|
|
import signal
|
|
|
|
|
2015-10-08 08:39:14 -05:00
|
|
|
import six
|
|
|
|
|
2015-06-11 08:04:31 -05:00
|
|
|
from ipapython import admintool, ipa_log_manager
|
2015-06-02 07:04:25 -05:00
|
|
|
from ipapython.ipautil import CheckedIPAddress, private_ccache
|
|
|
|
|
|
|
|
from . import core, common
|
|
|
|
|
|
|
|
__all__ = ['install_tool', 'uninstall_tool']
|
|
|
|
|
2015-10-08 08:39:14 -05:00
|
|
|
if six.PY3:
|
|
|
|
long = int
|
|
|
|
|
2015-06-02 07:04:25 -05:00
|
|
|
|
|
|
|
def install_tool(configurable_class, command_name, log_file_name,
|
2015-06-09 06:42:20 -05:00
|
|
|
positional_arguments=None, usage=None, debug_option=False,
|
2015-06-11 14:45:38 -05:00
|
|
|
use_private_ccache=True,
|
2015-06-09 06:41:09 -05:00
|
|
|
uninstall_log_file_name=None,
|
2015-06-09 06:42:20 -05:00
|
|
|
uninstall_positional_arguments=None, uninstall_usage=None):
|
2015-06-09 06:41:09 -05:00
|
|
|
if (uninstall_log_file_name is not None or
|
2015-06-09 06:42:20 -05:00
|
|
|
uninstall_positional_arguments is not None or
|
|
|
|
uninstall_usage is not None):
|
2015-06-02 07:04:25 -05:00
|
|
|
uninstall_kwargs = dict(
|
|
|
|
configurable_class=configurable_class,
|
|
|
|
command_name=command_name,
|
|
|
|
log_file_name=uninstall_log_file_name,
|
2015-06-09 06:41:09 -05:00
|
|
|
positional_arguments=uninstall_positional_arguments,
|
2015-06-09 06:42:20 -05:00
|
|
|
usage=uninstall_usage,
|
2015-06-02 07:04:25 -05:00
|
|
|
debug_option=debug_option,
|
|
|
|
)
|
|
|
|
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,
|
2015-06-09 06:41:09 -05:00
|
|
|
positional_arguments=positional_arguments,
|
2015-06-09 06:42:20 -05:00
|
|
|
usage=usage,
|
2015-06-02 07:04:25 -05:00
|
|
|
debug_option=debug_option,
|
|
|
|
uninstall_kwargs=uninstall_kwargs,
|
2015-06-11 14:45:38 -05:00
|
|
|
use_private_ccache=use_private_ccache,
|
2015-06-02 07:04:25 -05:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def uninstall_tool(configurable_class, command_name, log_file_name,
|
2015-06-09 06:42:20 -05:00
|
|
|
positional_arguments=None, usage=None, debug_option=False):
|
2015-06-02 07:04:25 -05:00
|
|
|
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,
|
2015-06-09 06:41:09 -05:00
|
|
|
positional_arguments=positional_arguments,
|
2015-06-09 06:42:20 -05:00
|
|
|
usage=usage,
|
2015-06-02 07:04:25 -05:00
|
|
|
debug_option=debug_option,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class ConfigureTool(admintool.AdminTool):
|
|
|
|
configurable_class = None
|
|
|
|
debug_option = False
|
2015-06-09 06:41:09 -05:00
|
|
|
positional_arguments = None
|
2015-06-11 14:45:38 -05:00
|
|
|
use_private_ccache = True
|
2015-06-02 07:04:25 -05:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _transform(configurable_class):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def add_options(cls, parser):
|
2015-08-06 01:16:45 -05:00
|
|
|
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",
|
|
|
|
)
|
|
|
|
|
2015-06-02 07:04:25 -05:00
|
|
|
basic_group = optparse.OptionGroup(parser, "basic options")
|
|
|
|
|
|
|
|
groups = collections.OrderedDict()
|
|
|
|
groups[None] = basic_group
|
|
|
|
|
|
|
|
for owner_cls, name in transformed_cls.knobs():
|
|
|
|
knob_cls = getattr(owner_cls, name)
|
2015-06-09 06:41:09 -05:00
|
|
|
if cls.positional_arguments and name in cls.positional_arguments:
|
|
|
|
continue
|
2015-06-02 07:04:25 -05:00
|
|
|
|
|
|
|
group_cls = owner_cls.group()
|
|
|
|
try:
|
|
|
|
opt_group = groups[group_cls]
|
|
|
|
except KeyError:
|
|
|
|
opt_group = groups[group_cls] = optparse.OptionGroup(
|
|
|
|
parser, "{0} options".format(group_cls.description))
|
|
|
|
|
|
|
|
kwargs = dict()
|
|
|
|
if knob_cls.type is bool:
|
|
|
|
kwargs['type'] = None
|
|
|
|
else:
|
|
|
|
kwargs['type'] = 'string'
|
|
|
|
kwargs['dest'] = name
|
|
|
|
kwargs['action'] = 'callback'
|
|
|
|
kwargs['callback'] = cls._option_callback
|
|
|
|
kwargs['callback_args'] = (knob_cls,)
|
|
|
|
if knob_cls.sensitive:
|
|
|
|
kwargs['sensitive'] = True
|
|
|
|
if knob_cls.cli_metavar:
|
|
|
|
kwargs['metavar'] = knob_cls.cli_metavar
|
|
|
|
|
|
|
|
if knob_cls.cli_short_name:
|
|
|
|
short_opt_str = '-{0}'.format(knob_cls.cli_short_name)
|
|
|
|
else:
|
|
|
|
short_opt_str = ''
|
2015-06-09 06:33:13 -05:00
|
|
|
cli_name = knob_cls.cli_name or name.replace('_', '-')
|
|
|
|
opt_str = '--{0}'.format(cli_name)
|
2015-06-02 07:04:25 -05:00
|
|
|
if not knob_cls.deprecated:
|
|
|
|
help = knob_cls.description
|
|
|
|
else:
|
|
|
|
help = optparse.SUPPRESS_HELP
|
|
|
|
opt_group.add_option(
|
|
|
|
short_opt_str, opt_str,
|
|
|
|
help=help,
|
|
|
|
**kwargs
|
|
|
|
)
|
|
|
|
|
|
|
|
if knob_cls.cli_aliases:
|
2015-06-09 06:33:13 -05:00
|
|
|
opt_strs = ['--{0}'.format(a) for a in knob_cls.cli_aliases]
|
2015-06-02 07:04:25 -05:00
|
|
|
opt_group.add_option(
|
2015-06-09 06:33:13 -05:00
|
|
|
*opt_strs,
|
2015-06-02 07:04:25 -05:00
|
|
|
help=optparse.SUPPRESS_HELP,
|
|
|
|
**kwargs
|
|
|
|
)
|
|
|
|
|
Use Python3-compatible dict method names
Python 2 has keys()/values()/items(), which return lists,
iterkeys()/itervalues()/iteritems(), which return iterators,
and viewkeys()/viewvalues()/viewitems() which return views.
Python 3 has only keys()/values()/items(), which return views.
To get iterators, one can use iter() or a for loop/comprehension;
for lists there's the list() constructor.
When iterating through the entire dict, without modifying the dict,
the difference between Python 2's items() and iteritems() is
negligible, especially on small dicts (the main overhead is
extra memory, not CPU time). In the interest of simpler code,
this patch changes many instances of iteritems() to items(),
iterkeys() to keys() etc.
In other cases, helpers like six.itervalues are used.
Reviewed-By: Christian Heimes <cheimes@redhat.com>
Reviewed-By: Jan Cholasta <jcholast@redhat.com>
2015-08-11 06:51:14 -05:00
|
|
|
for group, opt_group in groups.items():
|
2015-06-02 07:04:25 -05:00
|
|
|
parser.add_option_group(opt_group)
|
|
|
|
|
|
|
|
super(ConfigureTool, cls).add_options(parser,
|
|
|
|
debug_option=cls.debug_option)
|
|
|
|
|
|
|
|
@classmethod
|
2015-06-09 06:41:09 -05:00
|
|
|
def _option_callback(cls, option, opt_str, value, parser, knob_cls):
|
|
|
|
old_value = getattr(parser.values, option.dest, None)
|
|
|
|
try:
|
|
|
|
value = cls._parse_knob(knob_cls, old_value, value)
|
|
|
|
except ValueError as e:
|
|
|
|
raise optparse.OptionValueError(
|
|
|
|
"option {0}: {1}".format(opt_str, e))
|
|
|
|
|
|
|
|
setattr(parser.values, option.dest, value)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _parse_knob(cls, knob_cls, old_value, value):
|
|
|
|
if knob_cls.type is bool:
|
|
|
|
parse = bool
|
2015-06-02 07:04:25 -05:00
|
|
|
is_list = False
|
|
|
|
value = True
|
|
|
|
else:
|
2015-06-09 06:41:09 -05:00
|
|
|
if isinstance(knob_cls.type, tuple):
|
|
|
|
assert knob_cls.type[0] is list
|
|
|
|
value_type = knob_cls.type[1]
|
2015-06-02 07:04:25 -05:00
|
|
|
is_list = True
|
|
|
|
else:
|
2015-06-09 06:41:09 -05:00
|
|
|
value_type = knob_cls.type
|
2015-06-02 07:04:25 -05:00
|
|
|
is_list = False
|
|
|
|
|
2015-06-09 06:41:09 -05:00
|
|
|
if value_type is int:
|
|
|
|
def parse(value):
|
|
|
|
try:
|
|
|
|
return int(value, 0)
|
|
|
|
except ValueError:
|
|
|
|
raise ValueError(
|
|
|
|
"invalid integer value: {0}".format(repr(value)))
|
|
|
|
elif value_type is long:
|
|
|
|
def parse(value):
|
|
|
|
try:
|
|
|
|
return long(value, 0)
|
|
|
|
except ValueError:
|
|
|
|
raise ValueError(
|
|
|
|
"invalid long integer value: {0}".format(
|
|
|
|
repr(value)))
|
|
|
|
elif value_type == 'ip':
|
|
|
|
def parse(value):
|
|
|
|
try:
|
|
|
|
return CheckedIPAddress(value)
|
|
|
|
except Exception as e:
|
|
|
|
raise ValueError("invalid IP address {0}: {1}".format(
|
|
|
|
value, e))
|
2015-06-02 07:04:25 -05:00
|
|
|
elif value_type == 'ip-local':
|
2015-06-09 06:41:09 -05:00
|
|
|
def parse(value):
|
|
|
|
try:
|
|
|
|
return CheckedIPAddress(value, match_local=True)
|
|
|
|
except Exception as e:
|
|
|
|
raise ValueError("invalid IP address {0}: {1}".format(
|
|
|
|
value, e))
|
|
|
|
elif isinstance(value_type, set):
|
|
|
|
def parse(value):
|
|
|
|
if value not in value_type:
|
|
|
|
raise ValueError(
|
|
|
|
"invalid choice {0} (choose from {1})".format(
|
2015-11-06 03:05:42 -06:00
|
|
|
repr(value), ', '.join(
|
|
|
|
sorted(repr(v) for v in value_type))))
|
2015-06-09 06:41:09 -05:00
|
|
|
return value
|
|
|
|
else:
|
|
|
|
parse = value_type
|
2015-06-02 07:04:25 -05:00
|
|
|
|
2015-06-09 06:41:09 -05:00
|
|
|
value = parse(value)
|
2015-06-02 07:04:25 -05:00
|
|
|
|
|
|
|
if is_list:
|
2015-06-09 06:41:09 -05:00
|
|
|
old_value = old_value or []
|
2015-06-02 07:04:25 -05:00
|
|
|
old_value.append(value)
|
|
|
|
value = old_value
|
|
|
|
|
2015-06-09 06:41:09 -05:00
|
|
|
return value
|
2015-06-02 07:04:25 -05:00
|
|
|
|
|
|
|
def validate_options(self, needs_root=True):
|
|
|
|
super(ConfigureTool, self).validate_options(needs_root=needs_root)
|
|
|
|
|
2015-06-09 06:41:09 -05:00
|
|
|
if self.positional_arguments:
|
|
|
|
if len(self.args) > len(self.positional_arguments):
|
|
|
|
self.option_parser.error("Too many arguments provided")
|
|
|
|
|
|
|
|
index = 0
|
|
|
|
|
|
|
|
transformed_cls = self._transform(self.configurable_class)
|
|
|
|
for owner_cls, name in transformed_cls.knobs():
|
|
|
|
knob_cls = getattr(owner_cls, name)
|
|
|
|
if name not in self.positional_arguments:
|
|
|
|
continue
|
|
|
|
|
|
|
|
try:
|
|
|
|
value = self.args[index]
|
|
|
|
except IndexError:
|
|
|
|
break
|
|
|
|
|
|
|
|
old_value = getattr(self.options, name, None)
|
|
|
|
try:
|
|
|
|
value = self._parse_knob(knob_cls, old_value, value)
|
|
|
|
except ValueError as e:
|
|
|
|
self.option_parser.error(
|
|
|
|
"argument {0}: {1}".format(index + 1, e))
|
|
|
|
|
|
|
|
setattr(self.options, name, value)
|
|
|
|
|
|
|
|
index += 1
|
|
|
|
|
2015-06-11 08:04:31 -05:00
|
|
|
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
|
|
|
|
ipa_log_manager.standard_logging_setup(log_file_name,
|
|
|
|
debug=self.options.verbose)
|
|
|
|
self.log = ipa_log_manager.log_mgr.get_logger(self)
|
|
|
|
if log_file_name:
|
|
|
|
self.log.debug('Logging to %s' % log_file_name)
|
|
|
|
elif not no_file:
|
|
|
|
self.log.debug('Not logging to a file')
|
|
|
|
|
2015-06-02 07:04:25 -05:00
|
|
|
def run(self):
|
|
|
|
kwargs = {}
|
|
|
|
|
|
|
|
transformed_cls = self._transform(self.configurable_class)
|
2015-11-02 08:32:35 -06:00
|
|
|
knob_classes = {n: getattr(c, n) for c, n in transformed_cls.knobs()}
|
|
|
|
for name in knob_classes:
|
2015-06-02 07:04:25 -05:00
|
|
|
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:
|
2015-11-02 08:32:35 -06:00
|
|
|
knob_cls = knob_classes[e.name]
|
2015-06-09 06:41:09 -05:00
|
|
|
try:
|
2015-11-02 08:32:35 -06:00
|
|
|
if self.positional_arguments is None:
|
|
|
|
raise IndexError
|
2015-06-09 06:41:09 -05:00
|
|
|
index = self.positional_arguments.index(e.name)
|
|
|
|
except IndexError:
|
|
|
|
cli_name = knob_cls.cli_name 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))
|
2015-06-02 07:04:25 -05:00
|
|
|
except RuntimeError as e:
|
|
|
|
self.option_parser.error(str(e))
|
|
|
|
|
|
|
|
signal.signal(signal.SIGTERM, self.__signal_handler)
|
|
|
|
|
2015-06-11 14:45:38 -05:00
|
|
|
if self.use_private_ccache:
|
|
|
|
with private_ccache():
|
|
|
|
super(ConfigureTool, self).run()
|
|
|
|
cfgr.run()
|
|
|
|
else:
|
2015-06-02 07:04:25 -05:00
|
|
|
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):
|
|
|
|
super(InstallTool, cls).add_options(parser)
|
|
|
|
|
|
|
|
if cls.uninstall_kwargs is not None:
|
|
|
|
uninstall_group = optparse.OptionGroup(parser, "uninstall options")
|
|
|
|
uninstall_group.add_option(
|
|
|
|
'--uninstall',
|
|
|
|
dest='uninstall',
|
|
|
|
default=False,
|
|
|
|
action='store_true',
|
|
|
|
help=("uninstall an existing installation. The uninstall can "
|
|
|
|
"be run with --unattended option"),
|
|
|
|
)
|
|
|
|
parser.add_option_group(uninstall_group)
|
|
|
|
|
|
|
|
@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)
|