811 lines
30 KiB
Diff
811 lines
30 KiB
Diff
From b5c1ce88a4a3b35adb3b22bc68fb10b49322641a Mon Sep 17 00:00:00 2001
|
|
From: Petr Viktorin <pviktori@redhat.com>
|
|
Date: Fri, 20 Apr 2012 04:39:59 -0400
|
|
Subject: [PATCH 40/79] Framework for admin/install tools, with
|
|
ipa-ldap-updater
|
|
|
|
Currently, FreeIPA's install/admin scripts are long pieces of code
|
|
that aren't very reusable, importable, or testable.
|
|
They have been extended over time with features such as logging and
|
|
error handling, but since each tool was extended individually, there
|
|
is much inconsistency and code duplication.
|
|
This patch starts a framework which the admin tools can use, and
|
|
converts ipa-ldap-updater to use the framework.
|
|
|
|
Common tasks the tools do -- option parsing, validation, logging
|
|
setup, error handling -- are represented as methods. Individual
|
|
tools can extend, override or reuse the defaults as they see fit.
|
|
|
|
The ipa-ldap-updater has two modes (normal and --upgrade) that
|
|
don't share much functionality. They are represented by separate
|
|
classes. Option parsing, and selecting which class to run, happens
|
|
before they're instantiated.
|
|
|
|
All code is moved to importable modules to aid future testing. The
|
|
only thing that remains in the ipa-ldap-updater script is a two-line
|
|
call to the library.
|
|
|
|
First part of the work for:
|
|
https://fedorahosted.org/freeipa/ticket/2652
|
|
---
|
|
install/tools/ipa-ldap-updater | 160 +-----------------------
|
|
ipapython/admintool.py | 229 ++++++++++++++++++++++++++++++++++
|
|
ipaserver/install/installutils.py | 92 ++++++--------
|
|
ipaserver/install/ipa_ldap_updater.py | 189 ++++++++++++++++++++++++++++
|
|
ipaserver/install/ldapupdate.py | 2 +-
|
|
make-lint | 1 +
|
|
6 files changed, 463 insertions(+), 210 deletions(-)
|
|
create mode 100644 ipapython/admintool.py
|
|
create mode 100644 ipaserver/install/ipa_ldap_updater.py
|
|
|
|
diff --git a/install/tools/ipa-ldap-updater b/install/tools/ipa-ldap-updater
|
|
index 8f5c76645d9ba2b204f3b1051d9dc8b23eacce9d..0fc5a5bc448d04eb9ce8562ee1feebbf8f0fe356 100755
|
|
--- a/install/tools/ipa-ldap-updater
|
|
+++ b/install/tools/ipa-ldap-updater
|
|
@@ -20,162 +20,6 @@
|
|
|
|
# Documentation can be found at http://freeipa.org/page/LdapUpdate
|
|
|
|
-# TODO
|
|
-# save undo files?
|
|
+from ipaserver.install.ipa_ldap_updater import LDAPUpdater
|
|
|
|
-import os
|
|
-import sys
|
|
-try:
|
|
- from ipapython.config import IPAOptionParser
|
|
- from ipapython import ipautil, config
|
|
- from ipaserver.install import installutils
|
|
- from ipaserver.install.ldapupdate import LDAPUpdate, BadSyntax, UPDATES_DIR
|
|
- from ipaserver.install.upgradeinstance import IPAUpgrade
|
|
- from ipapython import sysrestore
|
|
- import krbV
|
|
- from ipalib import api
|
|
- from ipapython.ipa_log_manager import *
|
|
-except ImportError:
|
|
- print >> sys.stderr, """\
|
|
-There was a problem importing one of the required Python modules. The
|
|
-error was:
|
|
-
|
|
- %s
|
|
-""" % sys.exc_value
|
|
- sys.exit(1)
|
|
-
|
|
-def parse_options():
|
|
- usage = "%prog [options] input_file(s)\n"
|
|
- usage += "%prog [options]\n"
|
|
- parser = IPAOptionParser(usage=usage, formatter=config.IPAFormatter())
|
|
-
|
|
- parser.add_option("-d", "--debug", action="store_true", dest="debug",
|
|
- help="Display debugging information about the update(s)",
|
|
- default=False)
|
|
- parser.add_option("-t", "--test", action="store_true", dest="test",
|
|
- help="Run through the update without changing anything",
|
|
- default=False)
|
|
- parser.add_option("-y", dest="password",
|
|
- help="File containing the Directory Manager password")
|
|
- parser.add_option("-l", '--ldapi', action="store_true", dest="ldapi",
|
|
- default=False, help="Connect to the LDAP server using the ldapi socket")
|
|
- parser.add_option("-u", '--upgrade', action="store_true", dest="upgrade",
|
|
- default=False, help="Upgrade an installed server in offline mode")
|
|
- parser.add_option("-p", '--plugins', action="store_true", dest="plugins",
|
|
- default=False, help="Execute update plugins. Always true when applying all update files.")
|
|
- parser.add_option("-W", '--password', action="store_true",
|
|
- dest="ask_password",
|
|
- help="Prompt for the Directory Manager password")
|
|
-
|
|
- options, args = parser.parse_args()
|
|
- safe_options = parser.get_safe_opts(options)
|
|
-
|
|
- return safe_options, options, args
|
|
-
|
|
-def get_dirman_password():
|
|
- """Prompt the user for the Directory Manager password and verify its
|
|
- correctness.
|
|
- """
|
|
- password = installutils.read_password("Directory Manager", confirm=False, validate=False)
|
|
-
|
|
- return password
|
|
-
|
|
-def main():
|
|
- badsyntax = False
|
|
- upgradefailed = False
|
|
-
|
|
- safe_options, options, args = parse_options()
|
|
-
|
|
- run_plugins = options.plugins
|
|
-
|
|
- files = []
|
|
- if len(args) > 0:
|
|
- files = args
|
|
-
|
|
- if len(files) < 1:
|
|
- run_plugins = True
|
|
-
|
|
- if os.getegid() == 0:
|
|
- try:
|
|
- installutils.check_server_configuration()
|
|
- except RuntimeError, e:
|
|
- print unicode(e)
|
|
- sys.exit(1)
|
|
- else:
|
|
- if not os.path.exists('/etc/ipa/default.conf'):
|
|
- print "IPA is not configured on this system."
|
|
- sys.exit(1)
|
|
- if options.upgrade:
|
|
- sys.exit('Upgrade can only be done as root')
|
|
- if run_plugins:
|
|
- sys.exit('Plugins can only be run as root.')
|
|
-
|
|
- dirman_password = ""
|
|
- if options.password:
|
|
- pw = ipautil.template_file(options.password, [])
|
|
- dirman_password = pw.strip()
|
|
- else:
|
|
- if (options.ask_password or not options.ldapi) and not options.upgrade:
|
|
- dirman_password = get_dirman_password()
|
|
- if dirman_password is None:
|
|
- sys.exit("\nDirectory Manager password required")
|
|
-
|
|
- console_format = '%(levelname)s: %(message)s'
|
|
- if options.upgrade:
|
|
- standard_logging_setup('/var/log/ipaupgrade.log', debug=options.debug,
|
|
- console_format=console_format, filemode='a')
|
|
- else:
|
|
- standard_logging_setup(None, console_format=console_format,
|
|
- debug=options.debug)
|
|
-
|
|
- cfg = dict (
|
|
- in_server=True,
|
|
- context='updates',
|
|
- debug=options.debug,
|
|
- )
|
|
- api.bootstrap(**cfg)
|
|
- api.finalize()
|
|
-
|
|
- updates = None
|
|
- if options.upgrade:
|
|
- root_logger.debug('%s was invoked with arguments %s and options: %s' % (sys.argv[0], args, safe_options))
|
|
- realm = krbV.default_context().default_realm
|
|
- upgrade = IPAUpgrade(realm, files, live_run=not options.test)
|
|
- upgrade.create_instance()
|
|
- modified = upgrade.modified
|
|
- badsyntax = upgrade.badsyntax
|
|
- upgradefailed = upgrade.upgradefailed
|
|
- else:
|
|
- ld = LDAPUpdate(dm_password=dirman_password, sub_dict={}, live_run=not options.test, ldapi=options.ldapi, plugins=run_plugins)
|
|
- if len(files) < 1:
|
|
- files = ld.get_all_files(UPDATES_DIR)
|
|
- modified = ld.update(files)
|
|
-
|
|
- if badsyntax:
|
|
- root_logger.info('Bad syntax detected in upgrade file(s).')
|
|
- print 'Bad syntax detected in upgrade file(s).'
|
|
- return 1
|
|
- elif upgradefailed:
|
|
- root_logger.info('IPA upgrade failed.')
|
|
- print 'IPA upgrade failed.'
|
|
- return 1
|
|
- elif modified and options.test:
|
|
- root_logger.info('Update complete, changes to be made, test mode')
|
|
- return 2
|
|
- else:
|
|
- root_logger.info('Update complete')
|
|
- return 0
|
|
-
|
|
-try:
|
|
- if __name__ == "__main__":
|
|
- sys.exit(main())
|
|
-except BadSyntax, e:
|
|
- print "There is a syntax error in this update file:"
|
|
- print " %s" % e
|
|
- sys.exit(1)
|
|
-except RuntimeError, e:
|
|
- sys.exit(e)
|
|
-except SystemExit, e:
|
|
- sys.exit(e)
|
|
-except KeyboardInterrupt, e:
|
|
- sys.exit(1)
|
|
+LDAPUpdater.run_cli()
|
|
diff --git a/ipapython/admintool.py b/ipapython/admintool.py
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..60096e083ef209e943886cbe33189e5cdf063787
|
|
--- /dev/null
|
|
+++ b/ipapython/admintool.py
|
|
@@ -0,0 +1,229 @@
|
|
+# Authors:
|
|
+# Petr Viktorin <pviktori@redhat.com>
|
|
+#
|
|
+# Copyright (C) 2012 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/>.
|
|
+
|
|
+"""A common framework for command-line admin tools, e.g. install scripts
|
|
+
|
|
+Handles common operations like option parsing and logging
|
|
+"""
|
|
+
|
|
+import sys
|
|
+import os
|
|
+import traceback
|
|
+from optparse import OptionGroup
|
|
+
|
|
+from ipapython import version
|
|
+from ipapython import config
|
|
+from ipapython import ipa_log_manager
|
|
+
|
|
+
|
|
+class ScriptError(StandardError):
|
|
+ """An exception that records an error message and a return value
|
|
+ """
|
|
+ def __init__(self, msg='', rval=1):
|
|
+ self.msg = msg
|
|
+ self.rval = rval
|
|
+
|
|
+ def __str__(self):
|
|
+ return self.msg or ''
|
|
+
|
|
+
|
|
+class AdminTool(object):
|
|
+ """Base class for command-line admin tools
|
|
+
|
|
+ To run the tool, call the main() classmethod with a list of command-line
|
|
+ arguments.
|
|
+ Alternatively, call run_cli() to run with command-line arguments in
|
|
+ sys.argv, and call sys.exit() with the return value.
|
|
+
|
|
+ Some commands actually represent multiple related tools, e.g.
|
|
+ ``ipa-server-install`` and ``ipa-server-install --uninstall`` would be
|
|
+ represented by separate classes. Only their options are the same.
|
|
+
|
|
+ To handle this, AdminTool provides classmethods for option parsing
|
|
+ and selecting the appropriate command class.
|
|
+
|
|
+ A class-wide option parser is made by calling add_options.
|
|
+ The options are then parsed into options and arguments, and
|
|
+ get_command_class is called with those to retrieve the class.
|
|
+ That class is then instantiated and run.
|
|
+
|
|
+ Running consists of a few steps:
|
|
+ - validating options or the environment (validate_options)
|
|
+ - setting up logging (setup_logging)
|
|
+ - running the actual command (run)
|
|
+
|
|
+ Any unhandled exceptions are handled in handle_error.
|
|
+ And at the end, either log_success or log_failure is called.
|
|
+
|
|
+ Class attributes to define in subclasses:
|
|
+ command_name - shown in logs
|
|
+ log_file_name - if None, logging is to stderr only
|
|
+ needs_root - if true, non-root users can't run the tool
|
|
+ usage - text shown in help
|
|
+ """
|
|
+ command_name = None
|
|
+ log_file_name = None
|
|
+ needs_root = False
|
|
+ usage = None
|
|
+
|
|
+ _option_parsers = dict()
|
|
+
|
|
+ @classmethod
|
|
+ def make_parser(cls):
|
|
+ """Create an option parser shared across all instances of this class"""
|
|
+ parser = config.IPAOptionParser(version=version.VERSION,
|
|
+ usage=cls.usage, formatter=config.IPAFormatter())
|
|
+ cls.option_parser = parser
|
|
+ cls.add_options(parser)
|
|
+
|
|
+ @classmethod
|
|
+ def add_options(cls, parser):
|
|
+ """Add command-specific options to the option parser"""
|
|
+ parser.add_option("-d", "--debug", dest="debug", default=False,
|
|
+ action="store_true", help="print debugging information")
|
|
+
|
|
+ @classmethod
|
|
+ def run_cli(cls):
|
|
+ """Run this command with sys.argv, exit process with the return value
|
|
+ """
|
|
+ sys.exit(cls.main(sys.argv))
|
|
+
|
|
+ @classmethod
|
|
+ def main(cls, argv):
|
|
+ """The main entry point
|
|
+
|
|
+ Parses command-line arguments, selects the actual command class to use
|
|
+ based on them, and runs that command.
|
|
+
|
|
+ :param argv: Command-line arguments.
|
|
+ :return: Command exit code
|
|
+ """
|
|
+ if cls not in cls._option_parsers:
|
|
+ # We use cls._option_parsers, a dictionary keyed on class, to check
|
|
+ # if we need to create a parser. This is because cls.option_parser
|
|
+ # can refer to the parser of a superclass.
|
|
+ cls.make_parser()
|
|
+ cls._option_parsers[cls] = cls.option_parser
|
|
+
|
|
+ options, args = cls.option_parser.parse_args(argv[1:])
|
|
+
|
|
+ command_class = cls.get_command_class(options, args)
|
|
+ command = command_class(options, args)
|
|
+
|
|
+ return command.execute()
|
|
+
|
|
+ @classmethod
|
|
+ def get_command_class(cls, options, args):
|
|
+ return cls
|
|
+
|
|
+ def __init__(self, options, args):
|
|
+ self.options = options
|
|
+ self.args = args
|
|
+ self.safe_options = self.option_parser.get_safe_opts(options)
|
|
+
|
|
+ def execute(self):
|
|
+ """Do everything needed after options are parsed
|
|
+
|
|
+ This includes validating options, setting up logging, doing the
|
|
+ actual work, and handling the result.
|
|
+ """
|
|
+ try:
|
|
+ self.validate_options()
|
|
+ self.ask_for_options()
|
|
+ self.setup_logging()
|
|
+ return_value = self.run()
|
|
+ except BaseException, exception:
|
|
+ traceback = sys.exc_info()[2]
|
|
+ error_message, return_value = self.handle_error(exception)
|
|
+ if return_value:
|
|
+ self.log_failure(error_message, return_value, exception,
|
|
+ traceback)
|
|
+ return return_value
|
|
+ self.log_success()
|
|
+ return return_value
|
|
+
|
|
+ def validate_options(self):
|
|
+ """Validate self.options
|
|
+
|
|
+ It's also possible to compute and store information that will be
|
|
+ useful later, but no changes to the system should be made here.
|
|
+ """
|
|
+ if self.needs_root and os.getegid() != 0:
|
|
+ raise ScriptError('Must be root to run %s' % self.command_name, 1)
|
|
+
|
|
+ def ask_for_options(self):
|
|
+ """Ask for missing options interactively
|
|
+
|
|
+ Similar to validate_options. This is separate method because we want
|
|
+ any validation errors to abort the script before bothering the user
|
|
+ with prompts.
|
|
+ """
|
|
+ pass
|
|
+
|
|
+ def setup_logging(self):
|
|
+ """Set up logging"""
|
|
+ ipa_log_manager.standard_logging_setup(
|
|
+ self.log_file_name, debug=self.options.debug)
|
|
+ ipa_log_manager.log_mgr.get_logger(self, True)
|
|
+
|
|
+ def handle_error(self, exception):
|
|
+ """Given an exception, return a message (or None) and process exit code
|
|
+ """
|
|
+ if isinstance(exception, ScriptError):
|
|
+ return exception.msg, exception.rval or 1
|
|
+ elif isinstance(exception, SystemExit):
|
|
+ if isinstance(exception.code, int):
|
|
+ return None, exception.code
|
|
+ return str(exception.code), 1
|
|
+
|
|
+ return str(exception), 1
|
|
+
|
|
+ def run(self):
|
|
+ """Actual running of the command
|
|
+
|
|
+ This is where the hard work is done. The base implementation logs
|
|
+ the invocation of the command.
|
|
+
|
|
+ If this method returns (i.e. doesn't raise an exception), the tool is
|
|
+ assumed to have run successfully, and the return value is used as the
|
|
+ SystemExit code.
|
|
+ """
|
|
+ self.debug('%s was invoked with arguments %s and options: %s',
|
|
+ self.command_name, self.args, self.safe_options)
|
|
+
|
|
+ def log_failure(self, error_message, return_value, exception, backtrace):
|
|
+ try:
|
|
+ self.log
|
|
+ except AttributeError:
|
|
+ # Logging was not set up yet
|
|
+ print >> sys.stderr, '\n', error_message
|
|
+ else:
|
|
+ self.info(''.join(traceback.format_tb(backtrace)))
|
|
+ self.info('The %s command failed, exception: %s: %s',
|
|
+ self.command_name, type(exception).__name__, exception)
|
|
+ if error_message:
|
|
+ self.error(error_message)
|
|
+
|
|
+ def log_success(self):
|
|
+ try:
|
|
+ self.log
|
|
+ except AttributeError:
|
|
+ pass
|
|
+ else:
|
|
+ self.info('The %s command was successful', self.command_name)
|
|
diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
|
|
index 903e8f185c54182e800eda547b80b380f24ae8e4..388a11e26000a045a13c35ec54e02a2b5a2ea41e 100644
|
|
--- a/ipaserver/install/installutils.py
|
|
+++ b/ipaserver/install/installutils.py
|
|
@@ -32,12 +32,14 @@ import tempfile
|
|
import shutil
|
|
from ConfigParser import SafeConfigParser
|
|
import traceback
|
|
+import textwrap
|
|
|
|
from dns import resolver, rdatatype
|
|
from dns.exception import DNSException
|
|
import ldap
|
|
|
|
-from ipapython import ipautil, sysrestore
|
|
+from ipapython import ipautil, sysrestore, admintool
|
|
+from ipapython.admintool import ScriptError
|
|
from ipapython.ipa_log_manager import *
|
|
from ipalib.util import validate_hostname
|
|
from ipapython import config
|
|
@@ -61,18 +63,6 @@ class HostReverseLookupError(HostLookupError):
|
|
class HostnameLocalhost(HostLookupError):
|
|
pass
|
|
|
|
-
|
|
-class ScriptError(StandardError):
|
|
- """An exception that records an error message and a return value
|
|
- """
|
|
- def __init__(self, msg = '', rval = 1):
|
|
- self.msg = msg
|
|
- self.rval = rval
|
|
-
|
|
- def __str__(self):
|
|
- return self.msg
|
|
-
|
|
-
|
|
class ReplicaConfig:
|
|
def __init__(self):
|
|
self.realm_name = ""
|
|
@@ -639,65 +629,65 @@ def run_script(main_function, operation_name, log_file_name=None,
|
|
sys.exit(return_value)
|
|
|
|
except BaseException, error:
|
|
- handle_error(error, log_file_name)
|
|
+ message, exitcode = handle_error(error, log_file_name)
|
|
+ if message:
|
|
+ print >> sys.stderr, message
|
|
+ sys.exit(exitcode)
|
|
|
|
|
|
def handle_error(error, log_file_name=None):
|
|
- """Handle specific errors"""
|
|
+ """Handle specific errors. Returns a message and return code"""
|
|
|
|
if isinstance(error, SystemExit):
|
|
- sys.exit(error)
|
|
+ if isinstance(error.code, int):
|
|
+ return None, error.code
|
|
+ elif error.code is None:
|
|
+ return None, 0
|
|
+ else:
|
|
+ return str(error), 1
|
|
if isinstance(error, RuntimeError):
|
|
- sys.exit(error)
|
|
+ return str(error), 1
|
|
if isinstance(error, KeyboardInterrupt):
|
|
- print >> sys.stderr, "Cancelled."
|
|
- sys.exit(1)
|
|
+ return "Cancelled.", 1
|
|
|
|
- if isinstance(error, ScriptError):
|
|
- if error.msg:
|
|
- print >> sys.stderr, error.msg
|
|
- sys.exit(error.rval)
|
|
+ if isinstance(error, admintool.ScriptError):
|
|
+ return error.msg, error.rval
|
|
|
|
if isinstance(error, socket.error):
|
|
- print >> sys.stderr, error
|
|
- sys.exit(1)
|
|
+ return error, 1
|
|
|
|
if isinstance(error, ldap.INVALID_CREDENTIALS):
|
|
- print >> sys.stderr, "Invalid password"
|
|
- sys.exit(1)
|
|
+ return "Invalid password", 1
|
|
if isinstance(error, ldap.INSUFFICIENT_ACCESS):
|
|
- print >> sys.stderr, "Insufficient access"
|
|
- sys.exit(1)
|
|
+ return "Insufficient access", 1
|
|
if isinstance(error, ldap.LOCAL_ERROR):
|
|
- print >> sys.stderr, error.args[0]['info']
|
|
- sys.exit(1)
|
|
+ return error.args[0]['info'], 1
|
|
if isinstance(error, ldap.SERVER_DOWN):
|
|
- print >> sys.stderr, error.args[0]['desc']
|
|
- sys.exit(1)
|
|
+ return error.args[0]['desc'], 1
|
|
if isinstance(error, ldap.LDAPError):
|
|
- print >> sys.stderr, 'LDAP error: %s' % type(error).__name__
|
|
- print >> sys.stderr, error.args[0]['info']
|
|
- sys.exit(1)
|
|
+ return 'LDAP error: %s\n%s' % (
|
|
+ type(error).__name__, error.args[0]['info']), 1
|
|
|
|
if isinstance(error, config.IPAConfigError):
|
|
- print >> sys.stderr, "An IPA server to update cannot be found. Has one been configured yet?"
|
|
- print >> sys.stderr, "The error was: %s" % error
|
|
- sys.exit(1)
|
|
+ message = "An IPA server to update cannot be found. Has one been configured yet?"
|
|
+ message += "\nThe error was: %s" % error
|
|
+ return message, 1
|
|
if isinstance(error, errors.LDAPError):
|
|
- print >> sys.stderr, "An error occurred while performing operations: %s" % error
|
|
- sys.exit(1)
|
|
+ return "An error occurred while performing operations: %s" % error, 1
|
|
|
|
if isinstance(error, HostnameLocalhost):
|
|
- print >> sys.stderr, "The hostname resolves to the localhost address (127.0.0.1/::1)"
|
|
- print >> sys.stderr, "Please change your /etc/hosts file so that the hostname"
|
|
- print >> sys.stderr, "resolves to the ip address of your network interface."
|
|
- print >> sys.stderr, ""
|
|
- print >> sys.stderr, "Please fix your /etc/hosts file and restart the setup program"
|
|
- sys.exit(1)
|
|
+ message = textwrap.dedent("""
|
|
+ The hostname resolves to the localhost address (127.0.0.1/::1)
|
|
+ Please change your /etc/hosts file so that the hostname
|
|
+ resolves to the ip address of your network interface.
|
|
+
|
|
+ Please fix your /etc/hosts file and restart the setup program
|
|
+ """).strip()
|
|
+ return message, 1
|
|
|
|
if log_file_name:
|
|
- print >> sys.stderr, "Unexpected error - see %s for details:" % log_file_name
|
|
+ message = "Unexpected error - see %s for details:" % log_file_name
|
|
else:
|
|
- print >> sys.stderr, "Unexpected error"
|
|
- print >> sys.stderr, '%s: %s' % (type(error).__name__, error)
|
|
- sys.exit(1)
|
|
+ message = "Unexpected error"
|
|
+ message += '\n%s: %s' % (type(error).__name__, error)
|
|
+ return message, 1
|
|
diff --git a/ipaserver/install/ipa_ldap_updater.py b/ipaserver/install/ipa_ldap_updater.py
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..0c7d940be9b0d9777b7739dc5b63a0fe79b534b9
|
|
--- /dev/null
|
|
+++ b/ipaserver/install/ipa_ldap_updater.py
|
|
@@ -0,0 +1,189 @@
|
|
+#!/usr/bin/python
|
|
+# Authors: Rob Crittenden <rcritten@redhat.com>
|
|
+# Petr Viktorin <pviktori@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/>.
|
|
+#
|
|
+
|
|
+# Documentation can be found at http://freeipa.org/page/LdapUpdate
|
|
+
|
|
+# TODO
|
|
+# save undo files?
|
|
+
|
|
+import os
|
|
+
|
|
+import krbV
|
|
+
|
|
+from ipalib import api
|
|
+from ipapython import ipautil, admintool
|
|
+from ipaserver.install import installutils
|
|
+from ipaserver.install.ldapupdate import LDAPUpdate, UPDATES_DIR
|
|
+from ipaserver.install.upgradeinstance import IPAUpgrade
|
|
+from ipapython import ipa_log_manager
|
|
+
|
|
+
|
|
+class LDAPUpdater(admintool.AdminTool):
|
|
+ command_name = 'ipa-ldap-updater'
|
|
+
|
|
+ usage = "%prog [options] input_file(s)\n"
|
|
+ usage += "%prog [options]\n"
|
|
+
|
|
+ @classmethod
|
|
+ def add_options(cls, parser):
|
|
+ super(LDAPUpdater, cls).add_options(parser)
|
|
+
|
|
+ parser.add_option("-t", "--test", action="store_true", dest="test",
|
|
+ default=False,
|
|
+ help="Run through the update without changing anything")
|
|
+ parser.add_option("-y", dest="password",
|
|
+ help="File containing the Directory Manager password")
|
|
+ parser.add_option("-l", '--ldapi', action="store_true", dest="ldapi",
|
|
+ default=False,
|
|
+ help="Connect to the LDAP server using the ldapi socket")
|
|
+ parser.add_option("-u", '--upgrade', action="store_true",
|
|
+ dest="upgrade", default=False,
|
|
+ help="Upgrade an installed server in offline mode")
|
|
+ parser.add_option("-p", '--plugins', action="store_true",
|
|
+ dest="plugins", default=False,
|
|
+ help="Execute update plugins. " +
|
|
+ "Always true when applying all update files.")
|
|
+ parser.add_option("-W", '--password', action="store_true",
|
|
+ dest="ask_password",
|
|
+ help="Prompt for the Directory Manager password")
|
|
+
|
|
+ @classmethod
|
|
+ def get_command_class(cls, options, args):
|
|
+ if options.upgrade:
|
|
+ return LDAPUpdater_Upgrade
|
|
+ else:
|
|
+ return LDAPUpdater_NonUpgrade
|
|
+
|
|
+ def validate_options(self):
|
|
+ options = self.options
|
|
+ super(LDAPUpdater, self).validate_options()
|
|
+
|
|
+ self.files = self.args
|
|
+
|
|
+ for filename in self.files:
|
|
+ if not os.path.exists(filename):
|
|
+ raise admintool.ScriptError("%s: file not found" % filename)
|
|
+
|
|
+ if os.getegid() == 0:
|
|
+ installutils.check_server_configuration()
|
|
+ elif not os.path.exists('/etc/ipa/default.conf'):
|
|
+ raise admintool.ScriptError(
|
|
+ "IPA is not configured on this system.")
|
|
+
|
|
+ if options.password:
|
|
+ pw = ipautil.template_file(options.password, [])
|
|
+ self.dirman_password = pw.strip()
|
|
+ else:
|
|
+ self.dirman_password = None
|
|
+
|
|
+ def setup_logging(self):
|
|
+ ipa_log_manager.standard_logging_setup(self.log_file_name,
|
|
+ console_format='%(levelname)s: %(message)s',
|
|
+ debug=self.options.debug, filemode='a')
|
|
+ ipa_log_manager.log_mgr.get_logger(self, True)
|
|
+
|
|
+ def run(self):
|
|
+ super(LDAPUpdater, self).run()
|
|
+
|
|
+ api.bootstrap(
|
|
+ in_server=True,
|
|
+ context='updates',
|
|
+ debug=self.options.debug,
|
|
+ )
|
|
+ api.finalize()
|
|
+
|
|
+ def handle_error(self, exception):
|
|
+ return installutils.handle_error(exception, self.log_file_name)
|
|
+
|
|
+
|
|
+class LDAPUpdater_Upgrade(LDAPUpdater):
|
|
+ needs_root = True
|
|
+ log_file_name = '/var/log/ipaupgrade.log'
|
|
+
|
|
+ def validate_options(self):
|
|
+ if os.getegid() != 0:
|
|
+ raise admintool.ScriptError('Must be root to do an upgrade.', 1)
|
|
+
|
|
+ super(LDAPUpdater_Upgrade, self).validate_options()
|
|
+
|
|
+ def run(self):
|
|
+ super(LDAPUpdater_Upgrade, self).run()
|
|
+ options = self.options
|
|
+
|
|
+ updates = None
|
|
+ realm = krbV.default_context().default_realm
|
|
+ upgrade = IPAUpgrade(realm, self.files, live_run=not options.test)
|
|
+ upgrade.create_instance()
|
|
+ upgradefailed = upgrade.upgradefailed
|
|
+
|
|
+ if upgrade.badsyntax:
|
|
+ raise admintool.ScriptError(
|
|
+ 'Bad syntax detected in upgrade file(s).', 1)
|
|
+ elif upgrade.upgradefailed:
|
|
+ raise admintool.ScriptError('IPA upgrade failed.', 1)
|
|
+ elif upgrade.modified and options.test:
|
|
+ self.info('Update complete, changes to be made, test mode')
|
|
+ return 2
|
|
+
|
|
+
|
|
+class LDAPUpdater_NonUpgrade(LDAPUpdater):
|
|
+ def validate_options(self):
|
|
+ super(LDAPUpdater_NonUpgrade, self).validate_options()
|
|
+ options = self.options
|
|
+
|
|
+ # Only run plugins if no files are given
|
|
+ self.run_plugins = not self.files or options.plugins
|
|
+
|
|
+ # Need root for running plugins
|
|
+ if self.run_plugins and os.getegid() != 0:
|
|
+ raise admintool.ScriptError('Plugins can only be run as root.', 1)
|
|
+
|
|
+ def ask_for_options(self):
|
|
+ super(LDAPUpdater_NonUpgrade, self).ask_for_options()
|
|
+ options = self.options
|
|
+ if not self.dirman_password:
|
|
+ if options.ask_password or not options.ldapi:
|
|
+ password = installutils.read_password("Directory Manager",
|
|
+ confirm=False, validate=False)
|
|
+ if password is None:
|
|
+ raise admintool.ScriptError(
|
|
+ "Directory Manager password required")
|
|
+ self.dirman_password = password
|
|
+
|
|
+ def run(self):
|
|
+ super(LDAPUpdater_NonUpgrade, self).run()
|
|
+ options = self.options
|
|
+
|
|
+ ld = LDAPUpdate(
|
|
+ dm_password=self.dirman_password,
|
|
+ sub_dict={},
|
|
+ live_run=not options.test,
|
|
+ ldapi=options.ldapi,
|
|
+ plugins=options.plugins or self.run_plugins)
|
|
+
|
|
+ if not self.files:
|
|
+ self.files = ld.get_all_files(UPDATES_DIR)
|
|
+
|
|
+ modified = ld.update(self.files)
|
|
+
|
|
+ if modified and options.test:
|
|
+ self.info('Update complete, changes to be made, test mode')
|
|
+ return 2
|
|
diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
|
|
index e75ee804a1d201512f2770156eef73cf1bb1e7bb..c64139889d9f84866ac0cd358ed3a3a7d95af7dc 100644
|
|
--- a/ipaserver/install/ldapupdate.py
|
|
+++ b/ipaserver/install/ldapupdate.py
|
|
@@ -825,7 +825,7 @@ class LDAPUpdate:
|
|
data = self.read_file(f)
|
|
except Exception, e:
|
|
print e
|
|
- sys.exit(1)
|
|
+ sys.exit(e)
|
|
|
|
(all_updates, dn_list) = self.parse_update_file(data, all_updates, dn_list)
|
|
|
|
diff --git a/make-lint b/make-lint
|
|
index f619260434e33886175f5b7d5a1d008466f92a54..05a1bb14f3e1cfdb5404d9a0987f699f6d63428c 100755
|
|
--- a/make-lint
|
|
+++ b/make-lint
|
|
@@ -73,6 +73,7 @@ class IPATypeChecker(TypeChecker):
|
|
'ipalib.session.SessionManager' : ['log', 'debug', 'info', 'warning', 'error', 'critical', 'exception'],
|
|
'ipalib.session.SessionCCache' : ['log', 'debug', 'info', 'warning', 'error', 'critical', 'exception'],
|
|
'ipalib.session.MemcacheSessionManager' : ['log', 'debug', 'info', 'warning', 'error', 'critical', 'exception'],
|
|
+ 'ipapython.admintool.AdminTool' : ['log', 'debug', 'info', 'warning', 'error', 'critical', 'exception'],
|
|
}
|
|
|
|
def _related_classes(self, klass):
|
|
--
|
|
1.7.11.2
|
|
|