commit 5e0e0564f8ad2aa328bf9082f22dff8282a7add8
Author: Jan Pokorny <jpokorny(a)redhat.com>
Date: Fri Oct 30 17:23:35 2009 +0100
Added inital version of tool for DB import/export and password set.
I would like to finish import/export parts during next days.
luci_util.py | 418 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 418 insertions(+), 0 deletions(-)
---
diff --git a/luci_util.py b/luci_util.py
new file mode 100644
index 0000000..52e3c2d
--- /dev/null
+++ b/luci_util.py
@@ -0,0 +1,418 @@
+# -*- coding: utf-8 -*-
+
+"""Script for service routines connected with Luci.
+
+Actions available:
+ * set initial administrator's password/change current one
+ * import/export of the local database
+
+"""
+
+# Note to debugging:
+# Let the function/method to be debugged decorated with '_debug' decorator.
+# Special function 'debug_print()' serves for debug printing and is active
+# only when the corresponding function is decorated with '_debug'.
+# When function/method need no longer such kind of debugging, the decorator
+# can be removed and lines containing 'debug_print()' commented out.
+# When '_debug' decorator is used, it's definition can be commented out
+# or even removed from the code.
+
+
+__version__ = "0.1"
+
+
+from optparse import OptionParser, OptionGroup
+
+
+#
+# GLOBALS
+#
+
+USAGE = '%prog [-c <config file>] [-i <path>|-e <path>|-p
<password>]\n' \
+ 'Use -h or --help to display details.'
+VERSION = '%%prog %s' % __version__
+
+#DEFAULT_INI_LOCATION = '/var/lib/luci/'
+DEFAULT_INI_LOCATION='~/tg2env/luci'
+
+DEFAULT_INI_FILENAME='development.ini'
+
+
+#
+# FUNCTIONS AND DECORATORS FOR DEVELOPMENT/DEBUGGING
+#
+
+def _debug(fn):
+ """For decoration of functions/methods to use verbose debugging.
+
+ Is uses generally unrecommended technique, but for this purpose it
+ should be ok.
+
+ """
+ fn_name = fn.func_name
+
+ # Local version of debug_print that really does the job.
+ def debug_print(*args, **kwargs):
+ print args, kwargs
+
+ def decorator(*args, **kwargs):
+
+ # Backup current function connected with 'debug_print' identifier
+ # in func_globals. Then set the version that really does the job.
+ old_debug_print = fn.func_globals.get('debug_print', None)
+ fn.func_globals['debug_print'] = debug_print
+
+ print '--> %s():' % fn_name
+ input = ''
+ if len(args) > 0:
+ input += '\t- args: '
+ input += ', '.join([repr(s) for s in args])
+ if len(kwargs) > 0:
+ input += '\t- kwargs: '
+ input += ', '.join([repr(s) for kd in (kwargs.items())])
+ print '\tinput:\n%s' % input
+
+ # Perform the decorated functions.
+ retval = fn(*args, **kwargs)
+
+ # Restore 'debug_print'.
+ if old_debug_print:
+ fn.func_globals['debug_print'] = old_debug_print
+
+ # Print some basic debug messages, what was called with what arguments
+ # and that was returned.
+ print '<-- %s():' % fn_name
+ print '\toutput: %s' % repr(retval)
+
+ return retval
+
+ return decorator
+
+
+# Default empty debug printing. Do not redefine, use '@_debug' decorator
+# for functions/methods instead.
+def debug_print(s):
+ pass
+
+
+#
+# EXCEPTIONS
+#
+
+class LuciUtilError(Exception):
+ """Just for creating a 'identifiable type' of an
exception."""
+ pass
+
+
+#
+# HELPER FUNCTIONS
+#
+
+@_debug
+def _setParserOptions(parser):
+ """Set options for OptionParser instance.
+
+ The purpose is to gather setting of available options at one place.
+ Apart from it, it also serves to collect mutually exclusive options
+ to the lists of them, so this situation can be than easily discovered.
+
+ Keyword arguments:
+ parser OptionParser instance.
+
+ Return values:
+ (parser, mutex_opts_list) pair, where:
+ parser Passed instance of parser with some command-line options
+ added.
+ mutex_opts_list
+ List of lists of mutually exclusive options.
+
+ """
+
+ mutex_opts_list = []
+
+ mutex_opts_list.append([])
+ mutex_opts_index = 0
+
+ # ---
+ group_db = OptionGroup(parser, 'Whole database related options')
+ group_db.add_option('-i', '--import-db',
+ help='import existing database',
+ metavar='<path to DB file to import>',
+ action='store', type='string',
dest='import_src')
+ group_db.add_option('-e', '--export-db',
+ help='export current database',
+ metavar='<path to DB file where to export>',
+ action='store', type='string',
dest='export_dest')
+ mutex_opts_list[mutex_opts_index].append(group_db.get_option('-i'))
+ mutex_opts_list[mutex_opts_index].append(group_db.get_option('-e'))
+
+ # ---
+ group_misc = OptionGroup(parser, 'Miscellaneous options')
+ group_misc.add_option('-p', '--change-password',
+ help='change password (use \'-\' for direct input
afterwards)',
+ metavar='<password>',
+ action='store', type='string',
dest='pwd')
+ mutex_opts_list[mutex_opts_index].append(group_db.get_option('-p'))
+
+ # ---
+
+ parser.add_option_group(group_db)
+ parser.add_option_group(group_misc)
+
+ parser.add_option('-c', '--config',
+ #help='which configuration file for paster to use',
+ metavar='<configuration file for paster>',
+ action='store', type='string',
dest='config_file')
+
+ return parser, mutex_opts_list
+
+
+@_debug
+def _getTG2ConfigParser(config_file):
+ """Create and return (Safe)ConfigParser instance with config file
loaded.
+
+ If given path to config file is not an absolute path, its supposed that
+ given path is relative to DEFAULT_INI_LOCATION.
+
+ Keyword arguments:
+ config_file Path to the config file (usually with .ini extension).
+
+ Return values:
+ Created (Safe)ConfigParser instance with config file loaded.
+
+ Raises:
+ LuciUtilError
+
+ """
+ from os.path import isabs, split, normpath, join
+ from ConfigParser import SafeConfigParser
+
+ if not isabs(config_file):
+ config_file = join(DEFAULT_INI_LOCATION, config_file)
+
+ config_file = normpath(config_file)
+ config_file_dir = split(config_file)[0]
+
+ parser = SafeConfigParser({'here': config_file_dir})
+
+ if len(parser.read(config_file)) == 0:
+ raise LuciUtilError, "Could not find configuration file %s." \
+ % config_file
+
+ return parser
+
+
+@_debug
+def _SQLAlchemyURLFromConfig(config_file):
+ """Open given config file and get 'sqlalchemy.url' value from
[app:main].
+
+ Keyword arguments:
+ config_file Path to the config file (usually with .ini extension).
+
+ Return values:
+ 'sqlalchemy.url' value obtained from the given config file.
+
+ Raises:
+ LuciUtilError
+
+ """
+ from ConfigParser import Error
+
+ try:
+ config_parser = _getTG2ConfigParser(config_file)
+ sql_alchemy_url = config_parser.get('app:main',
'sqlalchemy.url')
+ except Error, e:
+ raise LuciUtilError, e
+
+ return sql_alchemy_url
+
+
+@_debug
+def _getDBFileFromSQLAlchemyURL(sql_alchemy_url):
+ """Convert 'sqlalchemy.url' value to path where such DB is
stored.
+
+ Keyword arguments:
+ sql_alchemy_url Value as given by _SQLAlchemyURLFromConfig().
+
+ Return values:
+ Path where the db defined by 'sqlalchemy.url' is stored.
+
+ Raises:
+ LuciUtilError
+
+ """
+ if issubclass(type(sql_alchemy_url), basestring):
+ sql_alchemy_url = sql_alchemy_url.strip()
+ if sql_alchemy_url.startswith('sqlite:///'):
+ return sql_alchemy_url.replace('sqlite:///', '', 1)
+ else:
+ raise LuciUtilError, "DB URL \`%s\' not supported." \
+ % config_file
+
+
+#
+# FUNCTIONS FOR PARTICULAR OPTIONS
+#
+
+@_debug
+def changePassword(config_file, password=None):
+ """Change password for the administrator in the database.
+
+ Keyword arguments:
+ config_file Path to the config file (usually with .ini extension).
+ password The password to be set or None to ask at a run-time.
+
+ Return values:
+
+
+ Raises:
+
+
+ """
+
+ # TODO -- Needs to be finished (Jan Pokorny will probably not be engaged
+ # in that).
+
+ from sqlalchemy import create_engine
+ from sqlalchemy import Table, Column, Integer, String, MetaData
+ from sqlalchemy.orm import mapper
+ from sqlalchemy.ext.declarative import declarative_base
+ from sqlalchemy.orm.exc import NoResultFound
+ from sqlalchemy.orm import sessionmaker
+
+ from getpass import getpass, GetPassWarning # To type the password
+ # at a runtime.
+
+ from luci.model.auth import User, Group, Permission
+
+ try:
+ sqlalchemy_url = _SQLAlchemyURLFromConfig(config_file)
+ except LuciUtilError, e:
+ print e
+ else:
+ # TODO: Following need to be rewritten, it was only my experiment.
+ engine = create_engine(sqlalchemy_url, echo=True) # TODO: echo=False
+ session = sessionmaker(bind=engine)
+ metadata = MetaData()
+ metadata.create_all(engine)
+
+ # Try to get the current data for given user. If it raises error,
+ # such column/table has to be created first. (???)
+ try:
+ result = session.query(User).filter_by(user_name=u'manager').one()
+ except NoResultFound, e:
+ print e
+
+
+@_debug
+def importDatabase(config_file, source, backup_old=False):
+ """Import the database from given source (replace the old one if
present).
+
+ If requested, the old DB file can be backed up.
+
+ Keyword arguments:
+ config_file Path to the config file (usually with .ini extension).
+ source Path of source DB file to be imported.
+ backup_old Whether to backup the old DB if was in place.
+
+ Return values:
+
+
+ Raises:
+
+
+ """
+ from shutil import copy
+
+ # TODO -- Jan Pokorny will have a look.
+
+ #
+ pass
+
+
+@_debug
+def exportDatabase(config_file, destination, overwrite=True):
+ """Export the database to given destination.
+
+ If requested, the file with the same name as a destination file
+ is NOT overwritten (if exists).
+
+ Keyword arguments:
+ config_file Path to the config file (usually with .ini extension).
+ destination Path to destination DB file, where to export DB file.
+ overwrite Whether to backup the old DB if was in place.
+
+ Return values:
+
+
+ Raises:
+
+
+ """
+
+ from shutil import copy
+
+ # TODO -- Jan Pokorny will have a look.
+
+ pass
+
+
+#
+# MAIN
+#
+
+@_debug
+def main():
+ (parser, mutex_opts_list) = _setParserOptions(
+ OptionParser(usage=USAGE, version=VERSION)
+ )
+ (options, args) = parser.parse_args()
+
+ debug_print(options)
+ debug_print(args)
+
+ # No stand-alone argument allowed.
+ if len(args) != 0:
+ parser.print_usage()
+ exit(1)
+
+ # Check, whether mutual exclusivity is kept.
+ for mutex_opts in mutex_opts_list:
+ i = 0
+ for mutex_opt in mutex_opts:
+ if getattr(options, mutex_opt.dest, False):
+ i += 1
+ if i > 1:
+ mutex_opts_str = ['\'' + opt._short_opts[0] + '\'' \
+ for opt in mutex_opts]
+ parser.error('all options %s are mutually exclusive' % \
+ ', '.join(mutex_opts_str))
+
+ # Set the configuration file to be used according to whether it is defined
+ # explicitly from command-line options or not.
+ if options.config_file:
+ config_file = options.config_file
+ else:
+ config_file = DEFAULT_INI_FILENAME
+
+ # Branch according to the option used.
+ if options.pwd:
+ # Minus as a password means to ask for it afterwards.
+ if options.pwd == '-':
+ changePassword(config_file)
+ else:
+ changePassword(config_file, options.pwd)
+ elif options.import_src:
+ importDatabase(config_file, options.import_src)
+ elif options.export_dest:
+ exportDatabase(config_file, options.export_dest)
+ else:
+ parser.print_usage()
+
+
+# -----------------------------------------------------------------------------
+
+if __name__ == '__main__':
+ # Module executed as a script.
+ main()
+