summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xinstall/tools/ipa-ldap-updater160
-rw-r--r--ipapython/admintool.py229
-rw-r--r--ipaserver/install/installutils.py92
-rw-r--r--ipaserver/install/ipa_ldap_updater.py189
-rw-r--r--ipaserver/install/ldapupdate.py2
-rwxr-xr-xmake-lint1
6 files changed, 463 insertions, 210 deletions
diff --git a/install/tools/ipa-ldap-updater b/install/tools/ipa-ldap-updater
index 8f5c7664..0fc5a5bc 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 00000000..60096e08
--- /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 903e8f18..388a11e2 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 00000000..0c7d940b
--- /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 e75ee804..c6413988 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 f6192604..05a1bb14 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):