[server] refactor cleanup() code used by "cubicweb-ctl delete"
authorAdrien Di Mascio <Adrien.DiMascio@logilab.fr>
Thu, 28 Mar 2013 19:24:46 +0100
changeset 10123 c390c444de06
parent 10122 67c947b4d2c9
child 10124 153c969b3a05
[server] refactor cleanup() code used by "cubicweb-ctl delete" Try to make each logical step (i.e. "delete database" and "delete user") a bit more visible and explicit. This is a preparatory work to easily plug the "delete namespace" feature.
server/serverctl.py
--- a/server/serverctl.py	Mon May 05 11:09:51 2014 +0200
+++ b/server/serverctl.py	Thu Mar 28 19:24:46 2013 +0100
@@ -24,6 +24,7 @@
 # completion). So import locally in command helpers.
 import sys
 import os
+from contextlib import contextmanager
 import logging
 import subprocess
 
@@ -31,6 +32,8 @@
 from logilab.common.configuration import Configuration, merge_options
 from logilab.common.shellutils import ASK, generate_password
 
+from logilab.database import get_db_helper, get_connection
+
 from cubicweb import AuthenticationError, ExecutionError, ConfigurationError
 from cubicweb.toolsutils import Command, CommandHandler, underline_title
 from cubicweb.cwctl import CWCTL, check_options_consistency, ConfigureInstanceCommand
@@ -47,7 +50,6 @@
     given server.serverconfig
     """
     from getpass import getpass
-    from logilab.database import get_connection, get_db_helper
     dbhost = source.get('db-host')
     if dbname is None:
         dbname = source['db-name']
@@ -104,7 +106,6 @@
     create/drop the instance database)
     """
     if dbms_system_base:
-        from logilab.database import get_db_helper
         system_db = get_db_helper(source['db-driver']).system_database()
         return source_cnx(source, system_db, special_privs=special_privs,
                           interactive=interactive)
@@ -116,7 +117,6 @@
     database)
     """
     import logilab.common as lgp
-    from logilab.database import get_db_helper
     lgp.USE_MX_DATETIME = False
     driver = source['db-driver']
     helper = get_db_helper(driver)
@@ -205,56 +205,71 @@
             print ('-> nevermind, you can do it later with '
                    '"cubicweb-ctl db-create %s".' % self.config.appid)
 
-ERROR = nullobject()
 
-def confirm_on_error_or_die(msg, func, *args, **kwargs):
+@contextmanager
+def db_sys_transaction(source, privilege):
+    """Open a transaction to the system database"""
+    cnx = _db_sys_cnx(source, privilege)
+    cursor = cnx.cursor()
     try:
-        return func(*args, **kwargs)
-    except Exception as ex:
-        print 'ERROR', ex
-        if not ASK.confirm('An error occurred while %s. Continue anyway?' % msg):
-            raise ExecutionError(str(ex))
-    return ERROR
+        yield cursor
+    except:
+        cnx.rollback()
+        cnx.close()
+        raise
+    else:
+        cnx.commit()
+        cnx.close()
+
 
 class RepositoryDeleteHandler(CommandHandler):
     cmdname = 'delete'
     cfgname = 'repository'
 
+    def _drop_database(self, source):
+        dbname = source['db-name']
+        if source['db-driver'] == 'sqlite':
+            print 'deleting database file %(db-name)s' % source
+            os.unlink(source['db-name'])
+            print '-> database %(db-name)s dropped.' % source
+        else:
+            helper = get_db_helper(source['db-driver'])
+            with db_sys_transaction(source, privilege='DROP DATABASE') as cursor:
+                print 'dropping database %(db-name)s' % source
+                cursor.execute('DROP DATABASE "%(db-name)s"' % source)
+                print '-> database %(db-name)s dropped.' % source
+
+    def _drop_user(self, source):
+        user = source['db-user'] or None
+        if user is not None:
+            with db_sys_transaction(source, privilege='DROP USER') as cursor:
+                print 'dropping user %s' % user
+                cursor.execute('DROP USER %s' % user)
+
+    def _cleanup_steps(self, source):
+        # 1/ delete database
+        yield ('Delete database "%(db-name)s"' % source,
+               self._drop_database, True)
+        # 2/ delete user
+        helper = get_db_helper(source['db-driver'])
+        if source['db-user'] and helper.users_support:
+            # XXX should check we are not connected as user
+            yield ('Delete user "%(db-user)s"' % source,
+                   self._drop_user, False)
+
     def cleanup(self):
         """remove instance's configuration and database"""
-        from logilab.database import get_db_helper
         source = self.config.system_source_config
-        dbname = source['db-name']
-        helper = get_db_helper(source['db-driver'])
-        if ASK.confirm('Delete database %s ?' % dbname):
-            if source['db-driver'] == 'sqlite':
-                if confirm_on_error_or_die(
-                    'deleting database file %s' % dbname,
-                    os.unlink, source['db-name']) is not ERROR:
-                    print '-> database %s dropped.' % dbname
-                return
-            user = source['db-user'] or None
-            cnx = confirm_on_error_or_die('connecting to database %s' % dbname,
-                                          _db_sys_cnx, source, 'DROP DATABASE')
-            if cnx is ERROR:
-                return
-            cursor = cnx.cursor()
-            try:
-                if confirm_on_error_or_die(
-                    'dropping database %s' % dbname,
-                    cursor.execute, 'DROP DATABASE "%s"' % dbname) is not ERROR:
-                    print '-> database %s dropped.' % dbname
-                # XXX should check we are not connected as user
-                if user and helper.users_support and \
-                       ASK.confirm('Delete user %s ?' % user, default_is_yes=False):
-                    if confirm_on_error_or_die(
-                        'dropping user %s' % user,
-                        cursor.execute, 'DROP USER %s' % user) is not ERROR:
-                        print '-> user %s dropped.' % user
-                cnx.commit()
-            except BaseException:
-                cnx.rollback()
-                raise
+        for msg, step, default in self._cleanup_steps(source):
+            if ASK.confirm(msg, default_is_yes=default):
+                try:
+                    step(source)
+                except Exception as exc:
+                    print 'ERROR', exc
+                    if ASK.confirm('An error occurred. Continue anyway?',
+                                   default_is_yes=False):
+                        continue
+                    raise ExecutionError(str(exc))
 
 
 class RepositoryStartHandler(CommandHandler):
@@ -294,6 +309,7 @@
         helper.create_database(cursor, source['db-name'],
                                dbencoding=source['db-encoding'], **kwargs)
 
+
 class CreateInstanceDBCommand(Command):
     """Create the system database of an instance (run after 'create').
 
@@ -331,7 +347,6 @@
 
     def run(self, args):
         """run the command with its specific arguments"""
-        from logilab.database import get_db_helper
         check_options_consistency(self.config)
         automatic = self.get('automatic')
         appid = args.pop()
@@ -439,7 +454,6 @@
         check_options_consistency(self.config)
         print '\n'+underline_title('Initializing the system database')
         from cubicweb.server import init_repository
-        from logilab.database import get_connection
         appid = args[0]
         config = ServerConfiguration.config_for(appid)
         try:
@@ -603,7 +617,6 @@
             sys.exit(1)
         cnx = source_cnx(sourcescfg['system'])
         driver = sourcescfg['system']['db-driver']
-        from logilab.database import get_db_helper
         dbhelper = get_db_helper(driver)
         cursor = cnx.cursor()
         # check admin exists