]> scripts.mit.edu Git - wizard.git/blobdiff - wizard/app/__init__.py
Reduce duplication in test scripts, more logging.
[wizard.git] / wizard / app / __init__.py
index adb9a189b6be8bbd58f557c5c47b781c888c2306..ee9f58deab43bc4990e0fc234033d540bd4f8c45 100644 (file)
@@ -6,6 +6,11 @@ You'll need to know how to overload the :class:`Application` class
 and use some of the functions in this module in order to specify
 new applications.
 
+There are some submodules for programming languages that define common
+functions and data that may be used by applications in that language.  See:
+
+* :mod:`wizard.app.php`
+
 .. testsetup:: *
 
     import re
@@ -76,6 +81,9 @@ class Application(object):
     #: a conflict marker string and a result list.  See :mod:`wizard.resolve`
     #: for more information.
     resolutions = {}
+    #: Instance of :class:`wizard.install.ArgSchema` that defines the arguments
+    #: this application requires.
+    install_schema = None
     def __init__(self, name):
         self.name = name
         self.versions = {}
@@ -222,19 +230,26 @@ class Application(object):
         should provide an implementation.
         """
         raise NotImplementedError
+    def detectVersionFromFile(self, filename, regex):
+        """
+        Helper method that detects a version by using a regular expression
+        from a file.  The regexed value is passed through :mod:`shlex`.
+        This assumes that the current working directory is the deployment.
+        """
+        contents = open(filename).read()
+        match = regex.search(contents)
+        if not match: return None
+        return distutils.version.LooseVersion(shlex.split(match.group(2))[0])
     def download(self, version):
         """
         Returns a URL that can be used to download a tarball of ``version`` of
         this application.
         """
         raise NotImplementedError
-    def checkWeb(self, deployment, output=None):
+    def checkWeb(self, deployment):
         """
-        Checks if the autoinstall is viewable from the web.  To get
-        the HTML source that was retrieved, pass a variable containing
-        an empty list to ``output``; it will be mutated to have its
-        first element be the output.  Subclasses should provide an
-        implementation.
+        Checks if the autoinstall is viewable from the web.  Subclasses should
+        provide an implementation.
 
         .. note::
             Finding a reasonable heuristic that works across skinning
@@ -244,6 +259,17 @@ class Application(object):
             not to depend on pages that are not the main page.
         """
         raise NotImplementedError
+    def checkWebPage(self, deployment, page, output):
+        """
+        Checks if a given page of an autoinstall contains a particular string.
+        """
+        page = deployment.fetch(page)
+        result = page.find(output) != -1
+        if result:
+            logging.debug("checkWebPage (passed):\n\n" + page)
+        else:
+            logging.info("checkWebPage (failed):\n\n" + page)
+        return result
     def checkConfig(self, deployment):
         """
         Checks whether or not an autoinstall has been configured/installed
@@ -361,7 +387,7 @@ def expand_re(val):
 
 def make_extractors(seed):
     """
-    Take a dictionary of ``key``s to ``(file, regex)`` tuples and convert them into
+    Take a dictionary of ``key`` to ``(file, regex)`` tuples and convert them into
     extractor functions (which take a :class:`wizard.deploy.Deployment`
     and return the value of the second subpattern of ``regex`` when matched
     with the contents of ``file``).
@@ -370,7 +396,7 @@ def make_extractors(seed):
 
 def make_substitutions(seed):
     """
-    Take a dictionary of ``key``s to ``(file, regex)`` tuples and convert them into substitution
+    Take a dictionary of ``key`` to ``(file, regex)`` tuples and convert them into substitution
     functions (which take a :class:`wizard.deploy.Deployment`, replace the second subpattern
     of ``regex`` with ``key`` in ``file``, and returns the number of substitutions made.)
     """
@@ -508,7 +534,7 @@ def get_mysql_args(d):
     # XXX: add support for getting these out of options
     vars = d.extract()
     if 'WIZARD_DBNAME' not in vars:
-        raise app.BackupFailure("Could not determine database name")
+        raise BackupFailure("Could not determine database name")
     triplet = scripts.get_sql_credentials(vars)
     args = []
     if triplet is not None:
@@ -522,10 +548,6 @@ class Error(wizard.Error):
     """Generic error class for this module."""
     pass
 
-class NonfatalFailure(Error):
-    """Bad parameters given to installer."""
-    pass
-
 class NoRepositoryError(Error):
     """
     :class:`Application` does not appear to have a Git repository
@@ -563,7 +585,33 @@ class NoSuchApplication(Error):
     def __init__(self, app):
         self.app = app
 
-class UpgradeFailure(Error):
+class Failure(Error):
+    """
+    Represents a failure when performing some double-dispatched operation
+    such as an installation or an upgrade.  Failure classes are postfixed
+    with Failure, not Error.
+    """
+    pass
+
+class InstallFailure(Error):
+    """Installation failed for unknown reason."""
+    def __str__(self):
+        return """Installation failed for unknown reason."""
+
+class RecoverableInstallFailure(InstallFailure):
+    """
+    Installation failed, but we were able to determine what the
+    error was, and should give the user a second chance if we were
+    running interactively.
+    """
+    #: List of the errors that were found.
+    errors = None
+    def __init__(self, errors):
+        self.errors = errors
+    def __str__(self):
+        return """Installation failed due to the following errors: %s""" % ", ".join(self.errors)
+
+class UpgradeFailure(Failure):
     """Upgrade script failed."""
     #: String details of failure (possibly stdout or stderr output)
     details = None
@@ -576,20 +624,15 @@ ERROR: Upgrade script failed, details:
 
 %s""" % self.details
 
-class UpgradeVerificationFailure(Error):
+class UpgradeVerificationFailure(Failure):
     """Upgrade script passed, but website wasn't accessible afterwards"""
-    #: String details of failure (possibly stdout or stderr output)
-    details = None
-    def __init__(self, details):
-        self.details = details
     def __str__(self):
         return """
 
-ERROR: Upgrade script passed, but website wasn't accessible afterwards. Details:
-
-%s""" % self.details
+ERROR: Upgrade script passed, but website wasn't accessible afterwards.  Check
+the debug logs for the contents of the page."""
 
-class BackupFailure(Error):
+class BackupFailure(Failure):
     """Backup script failed."""
     #: String details of failure
     details = None
@@ -602,7 +645,7 @@ ERROR: Backup script failed, details:
 
 %s""" % self.details
 
-class RestoreFailure(Error):
+class RestoreFailure(Failure):
     """Restore script failed."""
     #: String details of failure
     details = None