]> scripts.mit.edu Git - wizard.git/blobdiff - wizard/sql.py
Set admin e-mail address properly on MediaWiki >= 1.18.0
[wizard.git] / wizard / sql.py
index 5780dccd6bcb3e0c53bfce912dbde9f8773c3297..e1719fa48bbc6cc815111a02365653c34114c725 100644 (file)
@@ -1,7 +1,11 @@
 import sqlalchemy
 import os
+import pkg_resources
+import copy
+import decorator
 
-from wizard import shell
+import wizard
+from wizard import plugin, shell
 
 # We're going to use sqlalchemy.engine.url.URL as our database
 # info intermediate object
@@ -14,10 +18,13 @@ def connect(url):
     meta.reflect()
     return meta
 
-def fill_url(url):
+def auth(url):
     """
     If the URL has a database name but no other values, it will
     use the global configuration, and then try the database name.
+
+    This function implements a plugin interface named
+    :ref:`wizard.sql.auth`.
     """
     if not url:
         return None
@@ -28,15 +35,129 @@ def fill_url(url):
     if any((url.host, url.username, url.password)):
         # don't try for defaults if a few of these were set
         return url
-    # this is hook stuff
-    if url.driver == "mysql":
-        try:
-            url.host, url.username, url.password = shell.Shell().eval("/mit/scripts/sql/bin/get-password").split()
-            return url
-        except shell.CallError:
-            pass
-    dsn = os.getenv("WIZARD_DSN")
-    old_url = url
-    url = sqlalchemy.engine.url.make_url(dsn)
-    url.database = old_url.database
+    for entry in pkg_resources.iter_entry_points("wizard.sql.auth"):
+        func = entry.load()
+        r = func(copy.copy(url))
+        if r is not None:
+            return r
+    env_dsn = os.getenv("WIZARD_DSN")
+    if env_dsn:
+        old_url = url
+        url = sqlalchemy.engine.url.make_url(env_dsn)
+        url.database = old_url.database
     return url
+
+def backup(outdir, deployment):
+    """
+    Generic database backup function.
+    """
+    # XXX: Change this once deployments support multiple dbs
+    if deployment.application.database == "mysql":
+        return backup_mysql(outdir, deployment)
+    else:
+        raise NotImplementedError
+
+def backup_mysql(outdir, deployment):
+    """
+    Database backups for MySQL using the :command:`mysqldump` utility.
+    """
+    outfile = os.path.join(outdir, "db.sql")
+    try:
+        shell.call("mysqldump", "--compress", "-r", outfile, *get_mysql_args(deployment.dsn))
+        shell.call("gzip", "--best", outfile)
+    except shell.CallError as e:
+        raise BackupDatabaseError(e.stderr)
+
+def restore(backup_dir, deployment):
+    """
+    Generic database restoration function.
+    """
+    # XXX: see backup
+    if deployment.application.database == "mysql":
+        return restore_mysql(backup_dir, deployment)
+    else:
+        raise NotImplementedError
+
+def restore_mysql(backup_dir, deployment):
+    """
+    Database restoration for MySQL by piping SQL commands into :command:`mysql`.
+    """
+    if not os.path.exists(backup_dir):
+        raise RestoreDatabaseError("Backup %s doesn't exist" % backup_dir.rpartition("/")[2])
+    sql = open(os.path.join(backup_dir, "db.sql"), 'w+')
+    shell.call("gunzip", "-c", os.path.join(backup_dir, "db.sql.gz"), stdout=sql)
+    sql.seek(0)
+    shell.call("mysql", *get_mysql_args(deployment.dsn), stdin=sql)
+    sql.close()
+
+def drop(url):
+    """
+    Generic drop database function.  Attempts to run ``DROP
+    DATABASE`` on the database if no plugins succeed.
+
+    This function implements the plugin interface named
+    :ref:`wizard.sql.drop`.
+    """
+    r = plugin.hook("wizard.sql.drop", [url])
+    if r is not None:
+        return
+    engine = sqlalchemy.create_engine(url)
+    engine.execute("DROP DATABASE `%s`" % url.database)
+
+def get_mysql_args(dsn):
+    """
+    Extracts arguments that would be passed to the command line mysql utility
+    from a deployment.
+    """
+    args = []
+    if dsn.host:
+        args += ["-h", dsn.host]
+    if dsn.username:
+        args += ["-u", dsn.username]
+    if dsn.password:
+        args += ["-p" + dsn.password]
+    args += [dsn.database]
+    return args
+
+class Error(wizard.Error):
+    """Generic error class for this module."""
+    pass
+
+class BackupDatabaseError(Error):
+    """Backup script failed."""
+    #: String details of failure
+    details = None
+    def __init__(self, details):
+        self.details = details
+    def __str__(self):
+        return """
+
+ERROR: Backing up the database failed, details:
+
+%s""" % self.details
+
+class RestoreDatabaseError(Error):
+    """Restore script failed."""
+    #: String details of failure
+    details = None
+    def __init__(self, details):
+        self.details = details
+    def __str__(self):
+        return """
+
+ERROR: Restoring the database failed, details:
+
+%s""" % self.details
+
+class RemoveDatabaseError(Error):
+    """Removing the database failed."""
+    #: String details of failure
+    details = None
+    def __init__(self, details):
+        self.details = details
+    def __str__(self):
+        return """
+
+ERROR: Removing the database failed, details:
+
+%s""" % self.details